Merge commit 'd283ff9b1e3420e67f7decf700643baf2d791ea9' into dev-release
diff --git a/build.gradle b/build.gradle
index bce1569..171ede1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -506,6 +506,8 @@
 // Check if running with the JDK location from tools/jdk.py.
 if (OperatingSystem.current().isWindows()) {
     println "NOTE: Running with JDK: " + org.gradle.internal.jvm.Jvm.current().javaHome
+    compileJava.options.encoding = "UTF-8"
+    compileTestJava.options.encoding = "UTF-8"
 } else {
     def javaHomeOut = new StringBuilder()
     def javaHomeErr = new StringBuilder()
@@ -2354,7 +2356,7 @@
 allprojects {
   tasks.withType(Exec) {
     doFirst {
-      println commandLine
+      println commandLine.join(' ')
     }
   }
 }
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/BackportedMethodList.java b/src/main/java/com/android/tools/r8/BackportedMethodList.java
new file mode 100644
index 0000000..befc767
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/BackportedMethodList.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ExceptionUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThreadUtils;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Tool to extract the list of methods which is backported by the D8 and R8 compilers.
+ *
+ * <p>The D8 and R8 compilers will backport some simple Java APIs which are not present on all API
+ * levels. One example of this is the static method <code>int Integer.divideUnsigned(int a, int b)
+ * </code> which was added from API level 26.
+ *
+ * <p>As these backported methods is supported on all API levels, tools like linters and code
+ * checkers need this information to avoid false negatives when analyzing code.
+ *
+ * <p>This tool will generate a list of all the backported methods for the associated version of D8
+ * and R8.
+ *
+ * <p>This tool will <strong>not</strong> provide information about the APIs supported when using
+ * library desugaring. That information is provided in the dependencies used for library desugaring.
+ * However, in place of library desugaring backporting will be able to backport additional methods.
+ * If library desugaring is used, then passing information about that to this tool will provide the
+ * more precise list. See b/149078312.
+ *
+ * <p>The tool is invoked by calling {@link #run(BackportedMethodListCommand)
+ * BackportedMethodList.run} with an appropriate {@link BackportedMethodListCommand}.
+ *
+ * <p>For example:
+ *
+ * <pre>
+ *   BackportedMethodList.run(BackportedMethodListCommand.builder()
+ *       .setMinApiLevel(apiLevel)
+ *       .setOutputPath(Paths.get("methods-list.txt"))
+ *       .build());
+ * </pre>
+ *
+ * The above generates the list of backported methods for a compilation with a min API of <code>
+ * apiLevel</code> into the file <code>methods-list.txt</code>.
+ */
+@Keep
+public class BackportedMethodList {
+
+  static final String USAGE_MESSAGE =
+      StringUtils.joinLines(
+          "Usage: BackportedMethodList [options]",
+          " Options are:",
+          "  --output <file>         # Output result in <file>.",
+          "  --min-api <number>      # Minimum Android API level for the application",
+          "  --desugared-lib <file>  # Desugared library configuration (JSON from the",
+          "                          # configuration)",
+          "  --lib <file>            # The compilation SDK library (android.jar)",
+          "  --version               # Print the version of BackportedMethodList.",
+          "  --help                  # Print this message.");
+
+  private static String formatMethod(DexMethod method) {
+    return DescriptorUtils.getClassBinaryNameFromDescriptor(method.holder.descriptor.toString())
+        + '#'
+        + method.name
+        + method.proto.toDescriptorString();
+  }
+
+  public static void run(BackportedMethodListCommand command) throws CompilationFailedException {
+    if (command.isPrintHelp()) {
+      System.out.println(USAGE_MESSAGE);
+      return;
+    }
+    if (command.isPrintVersion()) {
+      System.out.println("BackportedMethodList " + Version.getVersionString());
+      return;
+    }
+    InternalOptions options = command.getInternalOptions();
+    ExecutorService executorService = ThreadUtils.getExecutorService(options);
+    try {
+      ExceptionUtils.withD8CompilationHandler(
+          command.getReporter(),
+          () -> {
+            BackportedMethodRewriter.generateListOfBackportedMethods(
+                    command.getInputApp(), options, executorService)
+                .stream()
+                .map(BackportedMethodList::formatMethod)
+                .sorted()
+                .forEach(
+                    formattedMethod ->
+                        command
+                            .getBackportedMethodListConsumer()
+                            .accept(formattedMethod, command.getReporter()));
+            command.getBackportedMethodListConsumer().finished(command.getReporter());
+          });
+    } finally {
+      executorService.shutdown();
+    }
+  }
+
+  public static void run(String[] args) throws CompilationFailedException {
+    run(BackportedMethodListCommand.parse(args).build());
+  }
+
+  public static void main(String[] args) {
+    ExceptionUtils.withMainProgramHandler(() -> run(args));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/BackportedMethodListCommand.java b/src/main/java/com/android/tools/r8/BackportedMethodListCommand.java
new file mode 100644
index 0000000..a4af022
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/BackportedMethodListCommand.java
@@ -0,0 +1,341 @@
+// Copyright (c) 2020, 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.graph.DexItemFactory;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
+import com.android.tools.r8.origin.Origin;
+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.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableSet;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Immutable command structure for an invocation of the {@link BackportedMethodList} tool.
+ *
+ * <p>To build a BackportedMethodList command use the {@link BackportedMethodListCommand.Builder}
+ * class. For example:
+ *
+ * <pre>
+ *   BackportedMethodListCommand command = BackportedMethodListCommand.builder()
+ *     .setMinApiLevel(apiLevel)
+ *     .setOutputPath(Paths.get("methods-list.txt"))
+ *     .build();
+ * </pre>
+ */
+@Keep
+public class BackportedMethodListCommand {
+
+  private final boolean printHelp;
+  private final boolean printVersion;
+  private final Reporter reporter;
+  private final int minApiLevel;
+  private final DesugaredLibraryConfiguration desugaredLibraryConfiguration;
+  private final AndroidApp app;
+  private final StringConsumer backportedMethodListConsumer;
+  private final DexItemFactory factory;
+
+  public boolean isPrintHelp() {
+    return printHelp;
+  }
+
+  public boolean isPrintVersion() {
+    return printVersion;
+  }
+
+  Reporter getReporter() {
+    return reporter;
+  }
+
+  public int getMinApiLevel() {
+    return minApiLevel;
+  }
+
+  public DesugaredLibraryConfiguration getDesugaredLibraryConfiguration() {
+    return desugaredLibraryConfiguration;
+  }
+
+  public StringConsumer getBackportedMethodListConsumer() {
+    return backportedMethodListConsumer;
+  }
+
+  AndroidApp getInputApp() {
+    return app;
+  }
+
+  private BackportedMethodListCommand(boolean printHelp, boolean printVersion) {
+    this.printHelp = printHelp;
+    this.printVersion = printVersion;
+    this.reporter = new Reporter();
+    this.minApiLevel = -1;
+    this.desugaredLibraryConfiguration = null;
+    this.app = null;
+    this.backportedMethodListConsumer = null;
+    this.factory = null;
+  }
+
+  private BackportedMethodListCommand(
+      Reporter reporter,
+      int minApiLevel,
+      DesugaredLibraryConfiguration desugaredLibraryConfiguration,
+      AndroidApp app,
+      StringConsumer backportedMethodListConsumer,
+      DexItemFactory factory) {
+    this.printHelp = false;
+    this.printVersion = false;
+    this.reporter = reporter;
+    this.minApiLevel = minApiLevel;
+    this.desugaredLibraryConfiguration = desugaredLibraryConfiguration;
+    this.app = app;
+    this.backportedMethodListConsumer = backportedMethodListConsumer;
+    this.factory = factory;
+  }
+
+  InternalOptions getInternalOptions() {
+    InternalOptions options = new InternalOptions(factory, getReporter());
+    options.minApiLevel = minApiLevel;
+    options.desugaredLibraryConfiguration = desugaredLibraryConfiguration;
+    return options;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static Builder builder(DiagnosticsHandler diagnosticsHandler) {
+    return new Builder(diagnosticsHandler);
+  }
+
+  public static Builder parse(String[] args) {
+    final Set<String> OPTIONS_WITH_PARAMETER =
+        ImmutableSet.of("--output", "--min-api", "--desugared-lib", "--lib");
+
+    boolean hasDefinedApiLevel = false;
+    Builder builder = builder();
+    for (int i = 0; i < args.length; i++) {
+      String arg = args[i].trim();
+      String nextArg = null;
+      if (OPTIONS_WITH_PARAMETER.contains(arg)) {
+        if (++i < args.length) {
+          nextArg = args[i];
+        } else {
+          builder.error(new StringDiagnostic("Missing parameter for " + args[i - 1] + "."));
+          break;
+        }
+      }
+      if (arg.equals("--help")) {
+        builder.setPrintHelp(true);
+      } else if (arg.equals("--version")) {
+        builder.setPrintVersion(true);
+      } else if (arg.equals("--min-api")) {
+        if (hasDefinedApiLevel) {
+          builder.error(new StringDiagnostic("Cannot set multiple --min-api options"));
+        } else {
+          parseMinApi(builder, nextArg);
+          hasDefinedApiLevel = true;
+        }
+      } else if (arg.equals("--desugared-lib")) {
+        builder.addDesugaredLibraryConfiguration(StringResource.fromFile(Paths.get(nextArg)));
+      } else if (arg.equals("--lib")) {
+        builder.addLibraryFiles(Paths.get(nextArg));
+      } else if (arg.equals("--output")) {
+        builder.setOutputPath(Paths.get(nextArg));
+      } else {
+        builder.error(new StringDiagnostic("Unknown option: " + arg));
+      }
+    }
+    return builder;
+  }
+
+  private static void parseMinApi(Builder builder, String minApiString) {
+    int minApi;
+    try {
+      minApi = Integer.parseInt(minApiString);
+    } catch (NumberFormatException e) {
+      builder.error(new StringDiagnostic("Invalid argument to --min-api: " + minApiString));
+      return;
+    }
+    if (minApi < 1) {
+      builder.error(new StringDiagnostic("Invalid argument to --min-api: " + minApiString));
+      return;
+    }
+    builder.setMinApiLevel(minApi);
+  }
+
+  @Keep
+  public static class Builder {
+
+    private final Reporter reporter;
+    private int minApiLevel = AndroidApiLevel.B.getLevel();
+    private List<StringResource> desugaredLibraryConfigurationResources = new ArrayList<>();
+    private final AndroidApp.Builder app;
+    private StringConsumer backportedMethodListConsumer;
+    private boolean printHelp = false;
+    private boolean printVersion = false;
+
+    private Builder() {
+      this(new DiagnosticsHandler() {});
+    }
+
+    private Builder(DiagnosticsHandler diagnosticsHandler) {
+      this.app = AndroidApp.builder();
+      this.reporter = new Reporter(diagnosticsHandler);
+    }
+
+    /**
+     * Set the minimum API level for the application compiled.
+     *
+     * <p>The tool will only report backported methods which are not present at this API level.
+     *
+     * <p>The default is 1 if never set.
+     */
+    public Builder setMinApiLevel(int minApiLevel) {
+      if (minApiLevel <= 0) {
+        reporter.error(new StringDiagnostic("Invalid minApiLevel: " + minApiLevel));
+      } else {
+        this.minApiLevel = minApiLevel;
+      }
+      return this;
+    }
+
+    public int getMinApiLevel() {
+      return minApiLevel;
+    }
+
+    /** Desugared library configuration */
+    public Builder addDesugaredLibraryConfiguration(StringResource configuration) {
+      desugaredLibraryConfigurationResources.add(configuration);
+      return this;
+    }
+
+    /** Desugared library configuration */
+    public Builder addDesugaredLibraryConfiguration(String configuration) {
+      return addDesugaredLibraryConfiguration(
+          StringResource.fromString(configuration, Origin.unknown()));
+    }
+
+    /** The compilation SDK library (android.jar) */
+    public Builder addLibraryResourceProvider(ClassFileResourceProvider provider) {
+      app.addLibraryResourceProvider(provider);
+      return this;
+    }
+
+    /** The compilation SDK library (android.jar) */
+    public Builder addLibraryFiles(Path... files) {
+      addLibraryFiles(Arrays.asList(files));
+      return this;
+    }
+
+    /** The compilation SDK library (android.jar) */
+    public Builder addLibraryFiles(Collection<Path> files) {
+      for (Path path : files) {
+        app.addLibraryFile(path);
+      }
+      return this;
+    }
+
+    DesugaredLibraryConfiguration getDesugaredLibraryConfiguration(DexItemFactory factory) {
+      if (desugaredLibraryConfigurationResources.isEmpty()) {
+        return DesugaredLibraryConfiguration.empty();
+      }
+      if (desugaredLibraryConfigurationResources.size() > 1) {
+        reporter.fatalError("Only one desugared library configuration is supported.");
+      }
+      StringResource desugaredLibraryConfigurationResource =
+          desugaredLibraryConfigurationResources.get(0);
+      DesugaredLibraryConfigurationParser libraryParser =
+          new DesugaredLibraryConfigurationParser(factory, null, false, getMinApiLevel());
+      return libraryParser.parse(desugaredLibraryConfigurationResource);
+    }
+
+    /** Output file for the backported method list */
+    public Builder setOutputPath(Path outputPath) {
+      backportedMethodListConsumer =
+          new StringConsumer.FileConsumer(outputPath) {
+            @Override
+            public void accept(String string, DiagnosticsHandler handler) {
+              super.accept(string, handler);
+              super.accept(System.lineSeparator(), handler);
+            }
+          };
+      return this;
+    }
+
+    /** Consumer receiving the the backported method list */
+    public Builder setConsumer(StringConsumer consumer) {
+      this.backportedMethodListConsumer = consumer;
+      return this;
+    }
+
+    /** True if the print-help flag is enabled. */
+    public boolean isPrintHelp() {
+      return printHelp;
+    }
+
+    /** Set the value of the print-help flag. */
+    public Builder setPrintHelp(boolean printHelp) {
+      this.printHelp = printHelp;
+      return this;
+    }
+
+    /** True if the print-version flag is enabled. */
+    public boolean isPrintVersion() {
+      return printVersion;
+    }
+
+    /** Set the value of the print-version flag. */
+    public Builder setPrintVersion(boolean printVersion) {
+      this.printVersion = printVersion;
+      return this;
+    }
+
+    private void error(Diagnostic diagnostic) {
+      reporter.error(diagnostic);
+    }
+
+    public BackportedMethodListCommand build() {
+      AndroidApp library = app.build();
+      if (!desugaredLibraryConfigurationResources.isEmpty()
+          && library.getLibraryResourceProviders().isEmpty()) {
+        reporter.error(
+            new StringDiagnostic("With desugared library configuration a library is required"));
+      }
+
+      if (isPrintHelp() || isPrintVersion()) {
+        return new BackportedMethodListCommand(isPrintHelp(), isPrintVersion());
+      }
+
+      if (backportedMethodListConsumer == null) {
+        backportedMethodListConsumer =
+            new StringConsumer() {
+              @Override
+              public void accept(String string, DiagnosticsHandler handler) {
+                System.out.println(string);
+              }
+
+              @Override
+              public void finished(DiagnosticsHandler handler) {}
+            };
+      }
+      DexItemFactory factory = new DexItemFactory();
+      return new BackportedMethodListCommand(
+          reporter,
+          minApiLevel,
+          getDesugaredLibraryConfiguration(factory),
+          library,
+          backportedMethodListConsumer,
+          factory);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 74b5d06..e46d5c1 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.ir.optimize.library.LibraryOptimizationInfoInitializer;
 import com.android.tools.r8.naming.PrefixRewritingNamingLens;
 import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.utils.AndroidApp;
@@ -178,6 +179,9 @@
       }
 
       AppView<?> appView = AppView.createForD8(appInfo, options, rewritePrefix);
+
+      new LibraryOptimizationInfoInitializer(appView).run();
+
       IRConverter converter = new IRConverter(appView, timing, printer);
       app = converter.convert(app, executor);
 
diff --git a/src/main/java/com/android/tools/r8/GenerateBackportedMethodList.java b/src/main/java/com/android/tools/r8/GenerateBackportedMethodList.java
deleted file mode 100644
index 90f577b..0000000
--- a/src/main/java/com/android/tools/r8/GenerateBackportedMethodList.java
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8;
-
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
-import com.android.tools.r8.utils.AndroidApiLevel;
-
-public class GenerateBackportedMethodList {
-
-  public static void main(String[] args) {
-    BackportedMethodRewriter.generateListOfBackportedMethods(AndroidApiLevel.B).stream()
-        .map(DexMethod::toSourceString)
-        .sorted()
-        .forEach(System.out::println);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 2aa0ac5..5e47207 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -10,9 +10,9 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerFactory;
 import com.android.tools.r8.shaking.MainDexClasses;
@@ -46,7 +46,7 @@
   private List<String> run(AndroidApp app, ExecutorService executor)
       throws IOException {
     try {
-      DexApplication application =
+      DirectMappedDexApplication application =
           new ApplicationReader(app, options, timing).read(executor).toDirect();
       AppView<? extends AppInfoWithSubtyping> appView =
           AppView.createForR8(new AppInfoWithSubtyping(application), options);
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
index 654b5aa..41c399f 100644
--- a/src/main/java/com/android/tools/r8/PrintSeeds.java
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerFactory;
@@ -84,7 +84,7 @@
     assert !options.forceProguardCompatibility;
     Timing timing = new Timing("PrintSeeds");
     try {
-      DexApplication application =
+      DirectMappedDexApplication application =
           new ApplicationReader(command.getInputApp(), options, timing).read(executor).toDirect();
       AppView<? extends AppInfoWithSubtyping> appView =
           AppView.createForR8(new AppInfoWithSubtyping(application), options);
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index ab5707a..d8d93ab 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
@@ -73,7 +74,7 @@
   private Map<DexType, Set<DexField>> fields = Maps.newIdentityHashMap();
   private Set<DexType> noObfuscationTypes = Sets.newIdentityHashSet();
   private Set<String> keepPackageNames = Sets.newHashSet();
-  private final DexApplication application;
+  private final DirectMappedDexApplication application;
   private final AppInfoWithSubtyping appInfo;
   private int errors;
 
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 70d4425..78471d6 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -24,11 +24,14 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
 import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis;
 import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.desugar.NestedPrivateMethodLense;
 import com.android.tools.r8.ir.desugar.R8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.EnumInfoMapCollector;
@@ -36,8 +39,11 @@
 import com.android.tools.r8.ir.optimize.NestReducer;
 import com.android.tools.r8.ir.optimize.SwitchMapCollector;
 import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization;
+import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.UninstantiatedTypeOptimizationGraphLense;
 import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector;
+import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector.UnusedArgumentsGraphLense;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.ir.optimize.library.LibraryOptimizationInfoInitializer;
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.kotlin.KotlinInfo;
@@ -75,6 +81,7 @@
 import com.android.tools.r8.shaking.TreePruner;
 import com.android.tools.r8.shaking.TreePrunerConfiguration;
 import com.android.tools.r8.shaking.VerticalClassMerger;
+import com.android.tools.r8.shaking.VerticalClassMergerGraphLense;
 import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
@@ -253,7 +260,7 @@
               "Running R8 version " + Version.LABEL + " with assertions enabled."));
     }
     try {
-      DexApplication application =
+      DirectMappedDexApplication application =
           new ApplicationReader(inputApp, options, timing).read(executorService).toDirect();
 
       // Now that the dex-application is fully loaded, close any internal archive providers.
@@ -314,13 +321,15 @@
                 .run(executorService));
 
         AppView<AppInfoWithLiveness> appViewWithLiveness = runEnqueuer(executorService, appView);
-        application = appViewWithLiveness.appInfo().app();
+        application = appViewWithLiveness.appInfo().app().asDirect();
         assert appView.rootSet().verifyKeptFieldsAreAccessedAndLive(appViewWithLiveness.appInfo());
         assert appView.rootSet().verifyKeptMethodsAreTargetedAndLive(appViewWithLiveness.appInfo());
         assert appView.rootSet().verifyKeptTypesAreLive(appViewWithLiveness.appInfo());
 
         appView.rootSet().checkAllRulesAreUsed(options);
 
+        new LibraryOptimizationInfoInitializer(appView).run();
+
         if (options.proguardSeedsConsumer != null) {
           ByteArrayOutputStream bytes = new ByteArrayOutputStream();
           PrintStream out = new PrintStream(bytes);
@@ -407,13 +416,12 @@
       if (options.shouldDesugarNests()) {
         timing.begin("NestBasedAccessDesugaring");
         R8NestBasedAccessDesugaring analyzer = new R8NestBasedAccessDesugaring(appViewWithLiveness);
-        boolean changed =
-            appView.setGraphLense(analyzer.run(executorService, application.builder()));
-        if (changed) {
+        NestedPrivateMethodLense lens = analyzer.run(executorService, application.builder());
+        if (lens != null) {
+          boolean changed = appView.setGraphLense(lens);
+          assert changed;
           appViewWithLiveness.setAppInfo(
-              appViewWithLiveness
-                  .appInfo()
-                  .rewrittenWithLense(application.asDirect(), appView.graphLense()));
+              appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
         }
         timing.end();
       } else {
@@ -429,12 +437,12 @@
         timing.begin("HorizontalStaticClassMerger");
         StaticClassMerger staticClassMerger =
             new StaticClassMerger(appViewWithLiveness, options, mainDexClasses);
-        boolean changed = appView.setGraphLense(staticClassMerger.run());
-        if (changed) {
+        NestedGraphLense lens = staticClassMerger.run();
+        if (lens != null) {
+          boolean changed = appView.setGraphLense(lens);
+          assert changed;
           appViewWithLiveness.setAppInfo(
-              appViewWithLiveness
-                  .appInfo()
-                  .rewrittenWithLense(application.asDirect(), appView.graphLense()));
+              appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
         }
         timing.end();
       }
@@ -443,46 +451,46 @@
         VerticalClassMerger verticalClassMerger =
             new VerticalClassMerger(
                 application, appViewWithLiveness, executorService, timing, mainDexClasses);
-        boolean changed = appView.setGraphLense(verticalClassMerger.run());
-        if (changed) {
+        VerticalClassMergerGraphLense lens = verticalClassMerger.run();
+        if (lens != null) {
+          boolean changed = appView.setGraphLense(lens);
+          assert changed;
           appView.setVerticallyMergedClasses(verticalClassMerger.getMergedClasses());
-          application = application.asDirect().rewrittenWithLense(appView.graphLense());
+          application = application.asDirect().rewrittenWithLens(lens);
+          lens.initializeCacheForLookupMethodInAllContexts();
           appViewWithLiveness.setAppInfo(
-              appViewWithLiveness
-                  .appInfo()
-                  .rewrittenWithLense(application.asDirect(), appView.graphLense()));
+              appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
+          lens.unsetCacheForLookupMethodInAllContexts();
         }
         timing.end();
       }
       if (options.enableArgumentRemoval) {
         if (options.enableUnusedArgumentRemoval) {
           timing.begin("UnusedArgumentRemoval");
-          boolean changed =
-              appView.setGraphLense(
-                  new UnusedArgumentsCollector(
-                          appViewWithLiveness, new MethodPoolCollection(appView))
-                      .run(executorService, timing));
-          if (changed) {
-            application = application.asDirect().rewrittenWithLense(appView.graphLense());
+          UnusedArgumentsGraphLense lens =
+              new UnusedArgumentsCollector(
+                      appViewWithLiveness, new MethodPoolCollection(appViewWithLiveness))
+                  .run(executorService, timing);
+          if (lens != null) {
+            boolean changed = appView.setGraphLense(lens);
+            assert changed;
+            assert application.asDirect().verifyNothingToRewrite(appView, lens);
             appViewWithLiveness.setAppInfo(
-                appViewWithLiveness
-                    .appInfo()
-                    .rewrittenWithLense(application.asDirect(), appView.graphLense()));
+                appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
           }
           timing.end();
         }
         if (options.enableUninstantiatedTypeOptimization) {
           timing.begin("UninstantiatedTypeOptimization");
-          boolean changed =
-              appView.setGraphLense(
-                  new UninstantiatedTypeOptimization(appViewWithLiveness)
-                      .run(new MethodPoolCollection(appView), executorService, timing));
-          if (changed) {
-            application = application.asDirect().rewrittenWithLense(appView.graphLense());
+          UninstantiatedTypeOptimizationGraphLense lens =
+              new UninstantiatedTypeOptimization(appViewWithLiveness)
+                  .run(new MethodPoolCollection(appViewWithLiveness), executorService, timing);
+          if (lens != null) {
+            boolean changed = appView.setGraphLense(lens);
+            assert changed;
+            assert application.asDirect().verifyNothingToRewrite(appView, lens);
             appViewWithLiveness.setAppInfo(
-                appViewWithLiveness
-                    .appInfo()
-                    .rewrittenWithLense(application.asDirect(), appView.graphLense()));
+                appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
           }
           timing.end();
         }
@@ -503,7 +511,7 @@
       CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
       try {
         IRConverter converter = new IRConverter(appView, timing, printer, mainDexClasses);
-        application = converter.optimize(executorService);
+        application = converter.optimize(executorService).asDirect();
       } finally {
         timing.end();
       }
@@ -688,7 +696,8 @@
 
       // Add automatic main dex classes to an eventual manual list of classes.
       if (!options.mainDexKeepRules.isEmpty()) {
-        application = application.builder().addToMainDexList(mainDexClasses.getClasses()).build();
+        application =
+            application.builder().addToMainDexList(mainDexClasses.getClasses()).build().asDirect();
       }
 
       // Perform minification.
@@ -878,7 +887,7 @@
     for (DexProgramClass programClass : application.classes()) {
       KotlinInfo kotlinInfo = kotlin.getKotlinInfo(programClass, reporter);
       programClass.setKotlinInfo(kotlinInfo);
-      KotlinMemberInfo.markKotlinMemberInfo(programClass, kotlinInfo);
+      KotlinMemberInfo.markKotlinMemberInfo(programClass, kotlinInfo, reporter);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/SwissArmyKnife.java b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
index d13081c..89aa64e 100644
--- a/src/main/java/com/android/tools/r8/SwissArmyKnife.java
+++ b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
@@ -79,6 +79,9 @@
       case "l8":
         L8.main(shift(args));
         break;
+      case "backportedmethods":
+        BackportedMethodList.main(shift(args));
+        break;
       default:
         runDefault(args);
         break;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
index cb4baef..d4d2066 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -28,6 +28,16 @@
   }
 
   @Override
+  public boolean isInstanceOf() {
+    return true;
+  }
+
+  @Override
+  public CfInstanceOf asInstanceOf() {
+    return this;
+  }
+
+  @Override
   public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitTypeInsn(Opcodes.INSTANCEOF, lens.lookupInternalName(type));
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 833e5e1..43d209e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -95,6 +95,14 @@
     return null;
   }
 
+  public boolean isInstanceOf() {
+    return false;
+  }
+
+  public CfInstanceOf asInstanceOf() {
+    return null;
+  }
+
   public boolean isStore() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/errors/InvalidDescriptorException.java b/src/main/java/com/android/tools/r8/errors/InvalidDescriptorException.java
new file mode 100644
index 0000000..f6702b7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/InvalidDescriptorException.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2020, 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.errors;
+
+public class InvalidDescriptorException extends InternalCompilerError {
+  public InvalidDescriptorException(String message) {
+    super(message);
+  }
+}
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 4e3df21..03f0231 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -111,7 +111,7 @@
     return builder.build();
   }
 
-  public Iterable<DexProgramClass> classes() {
+  public Collection<DexProgramClass> classes() {
     assert checkIfObsolete();
     return app.classes();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 96fee36..6630f9c 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -5,8 +5,11 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
+import java.util.Collections;
 import java.util.Deque;
+import java.util.Set;
 
 /* Specific subclass of AppInfo designed to support desugaring in D8. Desugaring requires a
  * minimal amount of knowledge in the overall program, provided through classpath. Basic
@@ -89,6 +92,57 @@
     return isSubtype(type, other) || isSubtype(other, type);
   }
 
+  /** Collect all interfaces that this type directly or indirectly implements. */
+  public Set<DexType> implementedInterfaces(DexType type) {
+    assert type.isClassType();
+    DexClass clazz = definitionFor(type);
+    if (clazz == null) {
+      return Collections.emptySet();
+    }
+
+    // Fast path for a type below object with no interfaces.
+    if (clazz.superType == dexItemFactory().objectType && clazz.interfaces.isEmpty()) {
+      return clazz.isInterface() ? Collections.singleton(type) : Collections.emptySet();
+    }
+
+    // Slow path traverses the full hierarchy.
+    Set<DexType> interfaces = Sets.newIdentityHashSet();
+    if (clazz.isInterface()) {
+      interfaces.add(type);
+    }
+    Deque<DexType> workList = new ArrayDeque<>();
+    if (clazz.superType != null && clazz.superType != dexItemFactory().objectType) {
+      workList.add(clazz.superType);
+    }
+    Collections.addAll(interfaces, clazz.interfaces.values);
+    Collections.addAll(workList, clazz.interfaces.values);
+    while (!workList.isEmpty()) {
+      DexType item = workList.pollFirst();
+      DexClass definition = definitionFor(item);
+      if (definition == null) {
+        // Collect missing types for future reporting?
+        continue;
+      }
+      if (definition.superType != null && definition.superType != dexItemFactory().objectType) {
+        workList.add(definition.superType);
+      }
+      for (DexType iface : definition.interfaces.values) {
+        if (interfaces.add(iface)) {
+          workList.add(iface);
+        }
+      }
+    }
+    return interfaces;
+  }
+
+  public boolean isExternalizable(DexType type) {
+    return isSubtype(type, dexItemFactory().externalizableType);
+  }
+
+  public boolean isSerializable(DexType type) {
+    return isSubtype(type, dexItemFactory().serializableType);
+  }
+
   /**
    * Helper method used for emulated interface resolution (not in JVM specifications). The result
    * may be abstract.
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 17e63e6..495c0b3 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_GROUP_CLASS_NAME_PREFIX;
 
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
@@ -14,19 +13,18 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Deque;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Consumer;
 import java.util.function.Function;
 
-public class AppInfoWithSubtyping extends AppInfoWithClassHierarchy {
+public class AppInfoWithSubtyping extends AppInfoWithClassHierarchy implements LiveSubTypeInfo {
 
   private static final int ROOT_LEVEL = 0;
   private static final int UNKNOWN_LEVEL = -1;
@@ -35,6 +33,19 @@
   // to add to it.
   private static final Set<DexType> NO_DIRECT_SUBTYPE = ImmutableSet.of();
 
+  @Override
+  public LiveSubTypeResult getLiveSubTypes(DexType type) {
+    // TODO(b/139464956): Remove this when we start to have live type information in the enqueuer.
+    Set<DexProgramClass> programClasses = new HashSet<>();
+    for (DexType subtype : subtypes(type)) {
+      DexProgramClass subClass = definitionForProgramType(subtype);
+      if (subClass != null) {
+        programClasses.add(subClass);
+      }
+    }
+    return new LiveSubTypeResult(programClasses, null);
+  }
+
   private static class TypeInfo {
 
     private final DexType type;
@@ -131,11 +142,16 @@
   private final Map<DexType, Boolean> mayHaveFinalizeMethodDirectlyOrIndirectlyCache =
       new ConcurrentHashMap<>();
 
-  public AppInfoWithSubtyping(DexApplication application) {
+  public AppInfoWithSubtyping(DirectMappedDexApplication application) {
+    this(application, application.allClasses());
+  }
+
+  public AppInfoWithSubtyping(
+      DirectMappedDexApplication application, Collection<DexClass> classes) {
     super(application);
     typeInfo = new ConcurrentHashMap<>();
     // Recompute subtype map if we have modified the graph.
-    populateSubtypeMap(application.asDirect(), application.dexItemFactory);
+    populateSubtypeMap(classes, application::definitionFor, application.dexItemFactory);
   }
 
   protected AppInfoWithSubtyping(AppInfoWithSubtyping previous) {
@@ -273,16 +289,19 @@
     }
   }
 
-  private void populateSubtypeMap(DirectMappedDexApplication app, DexItemFactory dexItemFactory) {
+  private void populateSubtypeMap(
+      Collection<DexClass> classes,
+      Function<DexType, DexClass> definitions,
+      DexItemFactory dexItemFactory) {
     getTypeInfo(dexItemFactory.objectType).tagAsSubtypeRoot();
     Map<DexType, Set<DexType>> map = new IdentityHashMap<>();
-    for (DexClass clazz : app.allClasses()) {
-      populateAllSuperTypes(map, clazz.type, clazz, app::definitionFor);
+    for (DexClass clazz : classes) {
+      populateAllSuperTypes(map, clazz.type, clazz, definitions);
     }
     for (Map.Entry<DexType, Set<DexType>> entry : map.entrySet()) {
       subtypeMap.put(entry.getKey(), ImmutableSet.copyOf(entry.getValue()));
     }
-    assert validateLevelsAreCorrect(app::definitionFor, dexItemFactory);
+    assert validateLevelsAreCorrect(definitions, dexItemFactory);
   }
 
   private boolean validateLevelsAreCorrect(
@@ -366,61 +385,6 @@
     return false;
   }
 
-  /**
-   * Resolve the methods implemented by the lambda expression that created the {@code callSite}.
-   *
-   * <p>If {@code callSite} was not created as a result of a lambda expression (i.e. the metafactory
-   * is not {@code LambdaMetafactory}), the empty set is returned.
-   *
-   * <p>If the metafactory is neither {@code LambdaMetafactory} nor {@code StringConcatFactory}, a
-   * warning is issued.
-   *
-   * <p>The returned set of methods all have {@code callSite.methodName} as the method name.
-   *
-   * @param callSite Call site to resolve.
-   * @return Methods implemented by the lambda expression that created the {@code callSite}.
-   */
-  public Set<DexEncodedMethod> lookupLambdaImplementedMethods(DexCallSite callSite) {
-    assert checkIfObsolete();
-    List<DexType> callSiteInterfaces = LambdaDescriptor.getInterfaces(callSite, this);
-    if (callSiteInterfaces == null || callSiteInterfaces.isEmpty()) {
-      return Collections.emptySet();
-    }
-    Set<DexEncodedMethod> result = new HashSet<>();
-    Deque<DexType> worklist = new ArrayDeque<>(callSiteInterfaces);
-    Set<DexType> visited = Sets.newIdentityHashSet();
-    while (!worklist.isEmpty()) {
-      DexType iface = worklist.removeFirst();
-      if (getTypeInfo(iface).isUnknown()) {
-        // Skip this interface. If the lambda only implements missing library interfaces and not any
-        // program interfaces, then minification and tree shaking are not interested in this
-        // DexCallSite anyway, so skipping this interface is harmless. On the other hand, if
-        // minification is run on a program with a lambda interface that implements both a missing
-        // library interface and a present program interface, then we might minify the method name
-        // on the program interface even though it should be kept the same as the (missing) library
-        // interface method. That is a shame, but minification is not suited for incomplete programs
-        // anyway.
-        continue;
-      }
-      if (!visited.add(iface)) {
-        // Already visited previously. May happen due to "diamond shapes" in the interface
-        // hierarchy.
-        continue;
-      }
-      assert getTypeInfo(iface).isInterface();
-      DexClass clazz = definitionFor(iface);
-      if (clazz != null) {
-        for (DexEncodedMethod method : clazz.virtualMethods()) {
-          if (method.method.name == callSite.methodName && method.accessFlags.isAbstract()) {
-            result.add(method);
-          }
-        }
-        Collections.addAll(worklist, clazz.interfaces.values);
-      }
-    }
-    return result;
-  }
-
   public boolean isStringConcat(DexMethodHandle bootstrapMethod) {
     assert checkIfObsolete();
     return bootstrapMethod.type.isInvokeStatic()
@@ -463,18 +427,8 @@
     return !getTypeInfo(type).directSubtypes.isEmpty();
   }
 
-  /**
-   * Apply the given function to all classes that directly extend this class.
-   *
-   * <p>If this class is an interface, then this method will visit all sub-interfaces. This deviates
-   * from the dex-file encoding, where subinterfaces "implement" their super interfaces. However, it
-   * is consistent with the source language.
-   */
-  public void forAllImmediateExtendsSubtypes(DexType type, Consumer<DexType> f) {
-    allImmediateExtendsSubtypes(type).forEach(f);
-  }
-
-  public Iterable<DexType> allImmediateExtendsSubtypes(DexType type) {
+  // TODO(b/139464956): Remove this method.
+  public Iterable<DexType> allImmediateExtendsSubtypes_(DexType type) {
     TypeInfo info = getTypeInfo(type);
     assert info.hierarchyLevel != UNKNOWN_LEVEL;
     if (info.hierarchyLevel == INTERFACE_LEVEL) {
@@ -487,18 +441,8 @@
     }
   }
 
-  /**
-   * Apply the given function to all classes that directly implement this interface.
-   *
-   * <p>The implementation does not consider how the hierarchy is encoded in the dex file, where
-   * interfaces "implement" their super interfaces. Instead it takes the view of the source
-   * language, where interfaces "extend" their superinterface.
-   */
-  public void forAllImmediateImplementsSubtypes(DexType type, Consumer<DexType> f) {
-    allImmediateImplementsSubtypes(type).forEach(f);
-  }
-
-  public Iterable<DexType> allImmediateImplementsSubtypes(DexType type) {
+  // TODO(b/139464956): Remove this method.
+  public Iterable<DexType> allImmediateImplementsSubtypes_(DexType type) {
     TypeInfo info = getTypeInfo(type);
     if (info.hierarchyLevel == INTERFACE_LEVEL) {
       return Iterables.filter(info.directSubtypes, subtype -> !getTypeInfo(subtype).isInterface());
@@ -511,48 +455,8 @@
     return clazz == null || clazz.hasMissingSuperType(this);
   }
 
-  public boolean isExternalizable(DexType type) {
-    return implementedInterfaces(type).contains(dexItemFactory().externalizableType);
-  }
-
-  public boolean isSerializable(DexType type) {
-    return implementedInterfaces(type).contains(dexItemFactory().serializableType);
-  }
-
-  /** Collect all interfaces that this type directly or indirectly implements. */
-  public Set<DexType> implementedInterfaces(DexType type) {
-    TypeInfo info = getTypeInfo(type);
-    if (info.implementedInterfaces != null) {
-      return info.implementedInterfaces;
-    }
-    synchronized (this) {
-      if (info.implementedInterfaces == null) {
-        Set<DexType> interfaces = Sets.newIdentityHashSet();
-        implementedInterfaces(type, interfaces);
-        info.implementedInterfaces = interfaces;
-      }
-    }
-    return info.implementedInterfaces;
-  }
-
-  private void implementedInterfaces(DexType type, Set<DexType> interfaces) {
-    DexClass dexClass = definitionFor(type);
-    // Loop to traverse the super type hierarchy of the current type.
-    while (dexClass != null) {
-      if (dexClass.isInterface()) {
-        interfaces.add(dexClass.type);
-      }
-      for (DexType itf : dexClass.interfaces.values) {
-        implementedInterfaces(itf, interfaces);
-      }
-      if (dexClass.superType == null) {
-        break;
-      }
-      dexClass = definitionFor(dexClass.superType);
-    }
-  }
-
-  public DexType getSingleSubtype(DexType type) {
+  // TODO(b/139464956): Remove this method.
+  public DexType getSingleSubtype_(DexType type) {
     TypeInfo info = getTypeInfo(type);
     assert info.hierarchyLevel != UNKNOWN_LEVEL;
     if (info.directSubtypes.size() == 1) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index d534cba..a471dc2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -232,4 +232,6 @@
   }
 
   public abstract DirectMappedDexApplication toDirect();
+
+  public abstract boolean isDirect();
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index d540b18..321b5c7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -779,11 +779,11 @@
     return isResolvable.isTrue();
   }
 
-  public boolean isSerializable(AppView<? extends AppInfoWithSubtyping> appView) {
+  public boolean isSerializable(AppView<? extends AppInfoWithClassHierarchy> appView) {
     return appView.appInfo().isSerializable(type);
   }
 
-  public boolean isExternalizable(AppView<? extends AppInfoWithSubtyping> appView) {
+  public boolean isExternalizable(AppView<? extends AppInfoWithClassHierarchy> appView) {
     return appView.appInfo().isExternalizable(type);
   }
 
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 193fd91..c38f7c6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -34,6 +34,7 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Streams;
@@ -121,6 +122,15 @@
   public final DexString voidDescriptor = createString("V");
   public final DexString descriptorSeparator = createString("/");
 
+  private final DexString booleanArrayDescriptor = createString("[Z");
+  private final DexString byteArrayDescriptor = createString("[B");
+  private final DexString charArrayDescriptor = createString("[C");
+  private final DexString doubleArrayDescriptor = createString("[D");
+  private final DexString floatArrayDescriptor = createString("[F");
+  private final DexString intArrayDescriptor = createString("[I");
+  private final DexString longArrayDescriptor = createString("[J");
+  private final DexString shortArrayDescriptor = createString("[S");
+
   public final DexString boxedBooleanDescriptor = createString("Ljava/lang/Boolean;");
   public final DexString boxedByteDescriptor = createString("Ljava/lang/Byte;");
   public final DexString boxedCharDescriptor = createString("Ljava/lang/Character;");
@@ -130,6 +140,7 @@
   public final DexString boxedLongDescriptor = createString("Ljava/lang/Long;");
   public final DexString boxedShortDescriptor = createString("Ljava/lang/Short;");
   public final DexString boxedNumberDescriptor = createString("Ljava/lang/Number;");
+  public final DexString boxedVoidDescriptor = createString("Ljava/lang/Void;");
 
   public final DexString unboxBooleanMethodName = createString("booleanValue");
   public final DexString unboxByteMethodName = createString("byteValue");
@@ -260,13 +271,14 @@
   public final DexString newUpdaterName = createString("newUpdater");
 
   public final DexString constructorMethodName = createString(Constants.INSTANCE_INITIALIZER_NAME);
-  public final DexString classConstructorMethodName = createString(Constants.CLASS_INITIALIZER_NAME);
+  public final DexString classConstructorMethodName =
+      createString(Constants.CLASS_INITIALIZER_NAME);
 
   public final DexString thisName = createString("this");
   public final DexString enumValuesFieldName = createString("$VALUES");
 
-  private final DexString charArrayDescriptor = createString("[C");
-  private final DexType charArrayType = createType(charArrayDescriptor);
+  public final DexString enabledFieldName = createString("ENABLED");
+
   public final DexString throwableArrayDescriptor = createString("[Ljava/lang/Throwable;");
 
   public final DexType booleanType = createType(booleanDescriptor);
@@ -279,6 +291,15 @@
   public final DexType shortType = createType(shortDescriptor);
   public final DexType voidType = createType(voidDescriptor);
 
+  public final DexType booleanArrayType = createType(booleanArrayDescriptor);
+  public final DexType byteArrayType = createType(byteArrayDescriptor);
+  public final DexType charArrayType = createType(charArrayDescriptor);
+  public final DexType doubleArrayType = createType(doubleArrayDescriptor);
+  public final DexType floatArrayType = createType(floatArrayDescriptor);
+  public final DexType intArrayType = createType(intArrayDescriptor);
+  public final DexType longArrayType = createType(longArrayDescriptor);
+  public final DexType shortArrayType = createType(shortArrayDescriptor);
+
   public final DexType boxedBooleanType = createType(boxedBooleanDescriptor);
   public final DexType boxedByteType = createType(boxedByteDescriptor);
   public final DexType boxedCharType = createType(boxedCharDescriptor);
@@ -288,6 +309,7 @@
   public final DexType boxedLongType = createType(boxedLongDescriptor);
   public final DexType boxedShortType = createType(boxedShortDescriptor);
   public final DexType boxedNumberType = createType(boxedNumberDescriptor);
+  public final DexType boxedVoidType = createType(boxedVoidDescriptor);
 
   public final DexType charSequenceType = createType(charSequenceDescriptor);
   public final DexType charSequenceArrayType = createType(charSequenceArrayDescriptor);
@@ -422,6 +444,7 @@
   public final DexType enumerationType = createType("Ljava/util/Enumeration;");
   public final DexType serializableType = createType("Ljava/io/Serializable;");
   public final DexType externalizableType = createType("Ljava/io/Externalizable;");
+  public final DexType cloneableType = createType("Ljava/lang/Cloneable;");
   public final DexType comparableType = createType("Ljava/lang/Comparable;");
 
   public final ServiceLoaderMethods serviceLoaderMethods = new ServiceLoaderMethods();
@@ -525,6 +548,9 @@
           classMethods.getName,
           classMethods.getSimpleName,
           classMethods.forName,
+          objectsMethods.requireNonNull,
+          objectsMethods.requireNonNullWithMessage,
+          objectsMethods.requireNonNullWithMessageSupplier,
           stringMethods.valueOf);
 
   // We assume library methods listed here are `public`, i.e., free from visibility side effects.
@@ -533,6 +559,7 @@
       Streams.<Pair<DexMethod, Predicate<InvokeMethod>>>concat(
               Stream.of(new Pair<>(enumMethods.constructor, alwaysTrue())),
               Stream.of(new Pair<>(objectMethods.constructor, alwaysTrue())),
+              Stream.of(new Pair<>(objectMethods.getClass, alwaysTrue())),
               mapToPredicate(classMethods.getNames, alwaysTrue()),
               mapToPredicate(
                   stringBufferMethods.constructorMethods,
@@ -678,11 +705,35 @@
 
   public class ObjectsMethods {
 
-    public DexMethod requireNonNull;
+    public final DexMethod requireNonNull;
+    public final DexMethod requireNonNullWithMessage;
+    public final DexMethod requireNonNullWithMessageSupplier;
 
     private ObjectsMethods() {
-      requireNonNull = createMethod(objectsDescriptor,
-          createString("requireNonNull"), objectDescriptor, new DexString[]{objectDescriptor});
+      DexString requireNonNullMethodName = createString("requireNonNull");
+      requireNonNull =
+          createMethod(objectsType, createProto(objectType, objectType), requireNonNullMethodName);
+      requireNonNullWithMessage =
+          createMethod(
+              objectsType,
+              createProto(objectType, objectType, stringType),
+              requireNonNullMethodName);
+      requireNonNullWithMessageSupplier =
+          createMethod(
+              objectsType,
+              createProto(objectType, objectType, supplierType),
+              requireNonNullMethodName);
+    }
+
+    public boolean isRequireNonNullMethod(DexMethod method) {
+      return method == requireNonNull
+          || method == requireNonNullWithMessage
+          || method == requireNonNullWithMessageSupplier;
+    }
+
+    public Iterable<DexMethod> requireNonNullMethods() {
+      return ImmutableList.of(
+          requireNonNull, requireNonNullWithMessage, requireNonNullWithMessageSupplier);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index f8aaa42..3e1ed8a 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -69,6 +69,10 @@
     return libraryClasses;
   }
 
+  public Collection<DexClasspathClass> classpathClasses() {
+    return classpathClasses;
+  }
+
   @Override
   public DexDefinition definitionFor(DexReference reference) {
     if (reference.isDexType()) {
@@ -126,6 +130,11 @@
   }
 
   @Override
+  public boolean isDirect() {
+    return true;
+  }
+
+  @Override
   public DirectMappedDexApplication asDirect() {
     return this;
   }
@@ -135,14 +144,24 @@
     return "DexApplication (direct)";
   }
 
-  public DirectMappedDexApplication rewrittenWithLense(GraphLense graphLense) {
+  public DirectMappedDexApplication rewrittenWithLens(GraphLense lens) {
     // As a side effect, this will rebuild the program classes and library classes maps.
-    DirectMappedDexApplication rewrittenApplication = this.builder().build().asDirect();
-    assert rewrittenApplication.mappingIsValid(graphLense, allClasses.keySet());
+    DirectMappedDexApplication rewrittenApplication = builder().build().asDirect();
+    assert rewrittenApplication.mappingIsValid(lens, allClasses.keySet());
     assert rewrittenApplication.verifyCodeObjectsOwners();
     return rewrittenApplication;
   }
 
+  public boolean verifyNothingToRewrite(AppView<?> appView, GraphLense lens) {
+    assert allClasses.keySet().stream()
+        .allMatch(
+            type ->
+                lens.lookupType(type) == type
+                    || appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(type));
+    assert verifyCodeObjectsOwners();
+    return true;
+  }
+
   private boolean mappingIsValid(GraphLense graphLense, Iterable<DexType> types) {
     // The lens might either map to a different type that is already present in the application
     // (e.g. relinking a type) or it might encode a type that was renamed, in which case the
@@ -192,7 +211,7 @@
   public static class Builder extends DexApplication.Builder<Builder> {
 
     private final ImmutableList<DexLibraryClass> libraryClasses;
-    private final ImmutableList<DexClasspathClass> classpathClasses;
+    private ImmutableList<DexClasspathClass> classpathClasses;
 
     Builder(LazyLoadedDexApplication application) {
       super(application);
@@ -214,8 +233,17 @@
       return this;
     }
 
+    public Builder addClasspathClasses(List<DexClasspathClass> classes) {
+      classpathClasses =
+          ImmutableList.<DexClasspathClass>builder()
+              .addAll(classpathClasses)
+              .addAll(classes)
+              .build();
+      return self();
+    }
+
     @Override
-    public DexApplication build() {
+    public DirectMappedDexApplication build() {
       // Rebuild the map. This will fail if keys are not unique.
       // TODO(zerny): Consider not rebuilding the map if no program classes are added.
       Map<DexType, DexClass> allClasses =
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
index 328bd61..b9e1b5c 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
@@ -16,6 +16,8 @@
 
   DexField getField();
 
+  int getNumberOfReadContexts();
+
   int getNumberOfWriteContexts();
 
   DexEncodedMethod getUniqueReadContext();
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
index 99ad00f..2b813ea 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -74,16 +74,23 @@
   }
 
   @Override
+  public int getNumberOfReadContexts() {
+    return getNumberOfAccessContexts(readsWithContexts);
+  }
+
+  @Override
   public int getNumberOfWriteContexts() {
-    if (writesWithContexts != null) {
-      if (writesWithContexts.size() == 1) {
-        return writesWithContexts.values().iterator().next().size();
-      } else {
-        throw new Unreachable(
-            "Should only be querying the number of write contexts after flattening");
-      }
+    return getNumberOfAccessContexts(writesWithContexts);
+  }
+
+  private int getNumberOfAccessContexts(Map<DexField, Set<DexEncodedMethod>> accessesWithContexts) {
+    if (accessesWithContexts == null) {
+      return 0;
     }
-    return 0;
+    if (accessesWithContexts.size() == 1) {
+      return accessesWithContexts.values().iterator().next().size();
+    }
+    throw new Unreachable("Should only be querying the number of access contexts after flattening");
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index afac334..f1d1a34 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -6,7 +6,6 @@
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
@@ -18,12 +17,10 @@
 import java.util.Deque;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
 import java.util.function.Function;
+import java.util.function.Supplier;
 
 /**
  * A GraphLense implements a virtual view on top of the graph, used to delay global rewrites until
@@ -169,7 +166,7 @@
 
   // This overload can be used when the graph lense is known to be context insensitive.
   public DexMethod lookupMethod(DexMethod method) {
-    assert isContextFreeForMethod(method);
+    assert verifyIsContextFreeForMethod(method);
     return lookupMethod(method, null, null).getMethod();
   }
 
@@ -180,12 +177,9 @@
 
   // Context sensitive graph lenses should override this method.
   public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
-    assert isContextFreeForMethod(method);
     DexMethod result = lookupMethod(method);
-    if (result != null) {
-      return ImmutableSet.of(result);
-    }
-    return ImmutableSet.of();
+    assert result != null;
+    return ImmutableSet.of(result);
   }
 
   public abstract DexField lookupField(DexField field);
@@ -221,7 +215,7 @@
   // an assertion error.
   public abstract boolean isContextFreeForMethods();
 
-  public boolean isContextFreeForMethod(DexMethod method) {
+  public boolean verifyIsContextFreeForMethod(DexMethod method) {
     return isContextFreeForMethods();
   }
 
@@ -261,33 +255,12 @@
     return true;
   }
 
-  public ImmutableList<DexReference> rewriteReferencesConservatively(List<DexReference> original) {
-    ImmutableList.Builder<DexReference> builder = ImmutableList.builder();
-    for (DexReference item : original) {
-      if (item.isDexMethod()) {
-        DexMethod method = item.asDexMethod();
-        if (isContextFreeForMethod(method)) {
-          builder.add(lookupMethod(method));
-        } else {
-          builder.addAll(lookupMethodInAllContexts(method));
-        }
-      } else {
-        builder.add(lookupReference(item));
-      }
-    }
-    return builder.build();
-  }
-
   public ImmutableSet<DexReference> rewriteReferencesConservatively(Set<DexReference> original) {
     ImmutableSet.Builder<DexReference> builder = ImmutableSet.builder();
     for (DexReference item : original) {
       if (item.isDexMethod()) {
         DexMethod method = item.asDexMethod();
-        if (isContextFreeForMethod(method)) {
-          builder.add(lookupMethod(method));
-        } else {
-          builder.addAll(lookupMethodInAllContexts(method));
-        }
+        builder.addAll(lookupMethodInAllContexts(method));
       } else {
         builder.add(lookupReference(item));
       }
@@ -295,23 +268,6 @@
     return builder.build();
   }
 
-  public Set<DexReference> rewriteMutableReferencesConservatively(Set<DexReference> original) {
-    Set<DexReference> result = Sets.newIdentityHashSet();
-    for (DexReference item : original) {
-      if (item.isDexMethod()) {
-        DexMethod method = item.asDexMethod();
-        if (isContextFreeForMethod(method)) {
-          result.add(lookupMethod(method));
-        } else {
-          result.addAll(lookupMethodInAllContexts(method));
-        }
-      } else {
-        result.add(lookupReference(item));
-      }
-    }
-    return result;
-  }
-
   public Object2BooleanMap<DexReference> rewriteReferencesConservatively(
       Object2BooleanMap<DexReference> original) {
     Object2BooleanMap<DexReference> result = new Object2BooleanArrayMap<>();
@@ -319,12 +275,8 @@
       DexReference item = entry.getKey();
       if (item.isDexMethod()) {
         DexMethod method = item.asDexMethod();
-        if (isContextFreeForMethod(method)) {
-          result.put(lookupMethod(method), entry.getBooleanValue());
-        } else {
-          for (DexMethod candidate : lookupMethodInAllContexts(method)) {
-            result.put(candidate, entry.getBooleanValue());
-          }
+        for (DexMethod candidate : lookupMethodInAllContexts(method)) {
+          result.put(candidate, entry.getBooleanValue());
         }
       } else {
         result.put(lookupReference(item), entry.getBooleanValue());
@@ -333,22 +285,6 @@
     return result;
   }
 
-  public ImmutableSet<DexType> rewriteTypesConservatively(Set<DexType> original) {
-    ImmutableSet.Builder<DexType> builder = ImmutableSet.builder();
-    for (DexType item : original) {
-      builder.add(lookupType(item));
-    }
-    return builder.build();
-  }
-
-  public Set<DexType> rewriteMutableTypesConservatively(Set<DexType> original) {
-    Set<DexType> result = Sets.newIdentityHashSet();
-    for (DexType item : original) {
-      result.add(lookupType(item));
-    }
-    return result;
-  }
-
   public ImmutableSortedSet<DexMethod> rewriteMethodsWithRenamedSignature(Set<DexMethod> methods) {
     ImmutableSortedSet.Builder<DexMethod> builder =
         new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare);
@@ -367,40 +303,12 @@
       }
     } else {
       for (DexMethod item : original) {
-        // Avoid using lookupMethodInAllContexts when possible.
-        if (isContextFreeForMethod(item)) {
-          builder.add(lookupMethod(item));
-        } else {
-          // The lense is context sensitive, but we do not have the context here. Therefore, we
-          // conservatively look up the method in all contexts.
-          builder.addAll(lookupMethodInAllContexts(item));
-        }
+        builder.addAll(lookupMethodInAllContexts(item));
       }
     }
     return builder.build();
   }
 
-  public SortedSet<DexMethod> rewriteMutableMethodsConservatively(Set<DexMethod> original) {
-    SortedSet<DexMethod> result = new TreeSet<>(PresortedComparable::slowCompare);
-    if (isContextFreeForMethods()) {
-      for (DexMethod item : original) {
-        result.add(lookupMethod(item));
-      }
-    } else {
-      for (DexMethod item : original) {
-        // Avoid using lookupMethodInAllContexts when possible.
-        if (isContextFreeForMethod(item)) {
-          result.add(lookupMethod(item));
-        } else {
-          // The lense is context sensitive, but we do not have the context here. Therefore, we
-          // conservatively look up the method in all contexts.
-          result.addAll(lookupMethodInAllContexts(item));
-        }
-      }
-    }
-    return result;
-  }
-
   public static <T extends DexReference, S> ImmutableMap<T, S> rewriteReferenceKeys(
       Map<T, S> original, Function<T, T> rewrite) {
     ImmutableMap.Builder<T, S> builder = new ImmutableMap.Builder<>();
@@ -410,15 +318,6 @@
     return builder.build();
   }
 
-  public static <T extends DexReference, S> Map<T, S> rewriteMutableReferenceKeys(
-      Map<T, S> original, Function<T, T> rewrite) {
-    Map<T, S> result = new IdentityHashMap<>();
-    for (T item : original.keySet()) {
-      result.put(rewrite.apply(item), original.get(item));
-    }
-    return result;
-  }
-
   public boolean verifyMappingToOriginalProgram(
       Iterable<DexProgramClass> classes,
       DexApplication originalApplication,
@@ -570,7 +469,7 @@
    */
   public static class NestedGraphLense extends GraphLense {
 
-    protected final GraphLense previousLense;
+    protected GraphLense previousLense;
     protected final DexItemFactory dexItemFactory;
 
     protected final Map<DexType, DexType> typeMap;
@@ -608,6 +507,14 @@
       this.dexItemFactory = dexItemFactory;
     }
 
+    public <T> T withAlternativeParentLens(GraphLense lens, Supplier<T> action) {
+      GraphLense oldParent = previousLense;
+      previousLense = lens;
+      T result = action.get();
+      previousLense = oldParent;
+      return result;
+    }
+
     @Override
     public DexType getOriginalType(DexType type) {
       return previousLense.getOriginalType(type);
@@ -761,8 +668,9 @@
     }
 
     @Override
-    public boolean isContextFreeForMethod(DexMethod method) {
-      return previousLense.isContextFreeForMethod(method);
+    public boolean verifyIsContextFreeForMethod(DexMethod method) {
+      assert previousLense.verifyIsContextFreeForMethod(method);
+      return true;
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index eb24b0c..9e96ab4 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -249,6 +249,11 @@
   }
 
   @Override
+  public boolean isDirect() {
+    return false;
+  }
+
+  @Override
   public String toString() {
     return "Application (" + programClasses + "; " + classpathClasses + "; " + libraryClasses
         + ")";
diff --git a/src/main/java/com/android/tools/r8/graph/LiveSubTypeInfo.java b/src/main/java/com/android/tools/r8/graph/LiveSubTypeInfo.java
new file mode 100644
index 0000000..f812d7e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/LiveSubTypeInfo.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2020, 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.graph;
+
+import java.util.Set;
+
+@FunctionalInterface
+public interface LiveSubTypeInfo {
+
+  LiveSubTypeResult getLiveSubTypes(DexType type);
+
+  class LiveSubTypeResult {
+
+    private final Set<DexProgramClass> programClasses;
+    private final Set<DexCallSite> callSites;
+
+    public LiveSubTypeResult(Set<DexProgramClass> programClasses, Set<DexCallSite> callSites) {
+      // TODO(b/149006127): Enable when we no longer rely on callSites == null.
+      this.programClasses = programClasses;
+      this.callSites = callSites;
+    }
+
+    public Set<DexProgramClass> getProgramClasses() {
+      return programClasses;
+    }
+
+    public Set<DexCallSite> getCallSites() {
+      return callSites;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index a25bab3..f3f308f 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -75,14 +75,7 @@
   public abstract DexEncodedMethod lookupInvokeStaticTarget(
       DexProgramClass context, AppInfoWithClassHierarchy appInfo);
 
-  public final Set<DexEncodedMethod> lookupVirtualDispatchTargets(
-      boolean isInterface, AppInfoWithSubtyping appInfo) {
-    return isInterface ? lookupInterfaceTargets(appInfo) : lookupVirtualTargets(appInfo);
-  }
-
-  public abstract Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo);
-
-  public abstract Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo);
+  public abstract Set<DexEncodedMethod> lookupVirtualDispatchTargets(AppInfoWithSubtyping appInfo);
 
   /** Result for a resolution that succeeds with a known declaration/definition. */
   public static class SingleResolutionResult extends ResolutionResult {
@@ -329,8 +322,15 @@
     }
 
     @Override
+    public Set<DexEncodedMethod> lookupVirtualDispatchTargets(AppInfoWithSubtyping appInfo) {
+      return initialResolutionHolder.isInterface()
+          ? lookupInterfaceTargets(appInfo)
+          : lookupVirtualTargets(appInfo);
+    }
+
     // TODO(b/140204899): Leverage refined receiver type if available.
-    public Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
+    private Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
+      assert !initialResolutionHolder.isInterface();
       if (resolvedMethod.isPrivateMethod()) {
         // If the resolved reference is private there is no dispatch.
         // This is assuming that the method is accessible, which implies self/nest access.
@@ -356,9 +356,9 @@
       return result;
     }
 
-    @Override
     // TODO(b/140204899): Leverage refined receiver type if available.
-    public Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
+    private Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
+      assert initialResolutionHolder.isInterface();
       if (resolvedMethod.isPrivateMethod()) {
         // If the resolved reference is private there is no dispatch.
         // This is assuming that the method is accessible, which implies self/nest access.
@@ -472,12 +472,7 @@
     }
 
     @Override
-    public final Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
-      return null;
-    }
-
-    @Override
-    public final Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
+    public Set<DexEncodedMethod> lookupVirtualDispatchTargets(AppInfoWithSubtyping appInfo) {
       return null;
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
index bed6235..fdb4dc1 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -22,19 +23,22 @@
 public class ClassInitializerAssertionEnablingAnalysis extends EnqueuerAnalysis {
   private final DexItemFactory dexItemFactory;
   private final OptimizationFeedback feedback;
+  private final DexString kotlinAssertionsEnabled;
 
   public ClassInitializerAssertionEnablingAnalysis(
       DexItemFactory dexItemFactory, OptimizationFeedback feedback) {
     this.dexItemFactory = dexItemFactory;
     this.feedback = feedback;
+    this.kotlinAssertionsEnabled = dexItemFactory.createString("ENABLED");
   }
 
   @Override
   public void processNewlyLiveMethod(DexEncodedMethod method) {
     if (method.isClassInitializer()) {
       if (method.getCode().isCfCode()) {
-        if (hasJavacClinitAssertionCode(method.getCode().asCfCode())) {
-          feedback.setInitializerEnablingJavaAssertions(method);
+        if (hasJavacClinitAssertionCode(method.getCode().asCfCode())
+            || hasKotlincClinitAssertionCode(method)) {
+          feedback.setInitializerEnablingJavaVmAssertions(method);
         }
       }
     }
@@ -70,6 +74,20 @@
   //  5: ixor
   // 13: putstatic     #<n>                // Field $assertionsDisabled:Z
 
+  // The <clinit> instruction sequence generated by kolinc for kotlin._Assertions.
+  //
+  //  0: new           #2                  // class kotlin/_Assertions
+  //  3: dup
+  //  4: invokespecial #31                 // Method "<init>":()V
+  //  7: astore_0
+  //  8: aload_0
+  //  9: putstatic     #33                 // Field INSTANCE:Lkotlin/_Assertions;
+  // 12: aload_0
+  // 13: invokevirtual #37                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
+  // 16: invokevirtual #43                 // Method java/lang/Class.desiredAssertionStatus:()Z
+  // 19: putstatic     #45                 // Field ENABLED:Z
+  // 22: return
+
   private static List<Class<?>> javacInstructionSequence =
       ImmutableList.of(
           CfIf.class,
@@ -108,6 +126,36 @@
     return false;
   }
 
+  private boolean hasKotlincClinitAssertionCode(DexEncodedMethod method) {
+    if (method.method.holder == dexItemFactory.kotlin.kotlinAssertions) {
+      CfCode code = method.getCode().asCfCode();
+      for (int i = 1; i < code.instructions.size(); i++) {
+        CfInstruction instruction = code.instructions.get(i - 1);
+        if (instruction.isInvoke()) {
+          // Check for the generated instruction sequence by looking for the call to
+          // desiredAssertionStatus() followed by the expected instruction types and finally
+          // checking the exact target of the putstatic ending the sequence.
+          CfInvoke invoke = instruction.asInvoke();
+          if (invoke.getOpcode() == Opcodes.INVOKEVIRTUAL
+              && invoke.getMethod() == dexItemFactory.classMethods.desiredAssertionStatus) {
+            if (code.instructions.get(i).isFieldInstruction()) {
+              CfFieldInstruction fieldInstruction = code.instructions.get(i).asFieldInstruction();
+              if (fieldInstruction.getOpcode() == Opcodes.PUTSTATIC
+                  && fieldInstruction.getField().name == kotlinAssertionsEnabled) {
+                return true;
+              }
+            }
+          }
+        }
+        // Only check initial straight line code.
+        if (instruction.isJump()) {
+          return false;
+        }
+      }
+    }
+    return false;
+  }
+
   private CfFieldInstruction isJavacInstructionSequence(CfCode code, int fromIndex) {
     List<Class<?>> sequence = javacInstructionSequence;
     int nextExpectedInstructionIndex = 0;
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
new file mode 100644
index 0000000..c85439b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
@@ -0,0 +1,143 @@
+// Copyright (c) 2020, 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.graph.analysis;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClasspathClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.Mode;
+import com.android.tools.r8.utils.OptionalBool;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+
+public class DesugaredLibraryConversionWrapperAnalysis extends EnqueuerAnalysis
+    implements EnqueuerInvokeAnalysis {
+
+  private final AppView<?> appView;
+  private final DesugaredLibraryAPIConverter converter;
+  private boolean callbackGenerated = false;
+  private Map<DexProgramClass, DexProgramClass> wrappersToReverseMap = null;
+
+  public DesugaredLibraryConversionWrapperAnalysis(AppView<?> appView) {
+    this.appView = appView;
+    this.converter =
+        new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS);
+  }
+
+  @Override
+  public void processNewlyLiveMethod(DexEncodedMethod method) {
+    converter.registerCallbackIfRequired(method);
+  }
+
+  private void traceInvoke(DexMethod invokedMethod) {
+    converter.registerWrappersForLibraryInvokeIfRequired(invokedMethod);
+  }
+
+  @Override
+  public void traceInvokeStatic(DexMethod invokedMethod, ProgramMethod context) {
+    this.traceInvoke(invokedMethod);
+  }
+
+  @Override
+  public void traceInvokeDirect(DexMethod invokedMethod, ProgramMethod context) {
+    this.traceInvoke(invokedMethod);
+  }
+
+  @Override
+  public void traceInvokeInterface(DexMethod invokedMethod, ProgramMethod context) {
+    this.traceInvoke(invokedMethod);
+  }
+
+  @Override
+  public void traceInvokeSuper(DexMethod invokedMethod, ProgramMethod context) {
+    this.traceInvoke(invokedMethod);
+  }
+
+  @Override
+  public void traceInvokeVirtual(DexMethod invokedMethod, ProgramMethod context) {
+    this.traceInvoke(invokedMethod);
+  }
+
+  public List<DexEncodedMethod> generateCallbackMethods() {
+    assert !callbackGenerated;
+    callbackGenerated = true;
+    return converter.generateCallbackMethods();
+  }
+
+  public Set<DexProgramClass> generateWrappers() {
+    assert wrappersToReverseMap == null;
+    wrappersToReverseMap = converter.synthesizeWrappersAndMapToReverse();
+    return wrappersToReverseMap.keySet();
+  }
+
+  // Generate a mock classpath class for all vivified types.
+  // Types will be available at runtime in the desugared library dex file.
+  public List<DexClasspathClass> generateWrappersSuperTypeMock() {
+    List<DexClasspathClass> classpathClasses = new ArrayList<>();
+    for (DexProgramClass wrapper : wrappersToReverseMap.keySet()) {
+      boolean mockIsInterface = wrapper.interfaces.size() == 1;
+      DexType mockType = mockIsInterface ? wrapper.interfaces.values[0] : wrapper.superType;
+      if (appView.definitionFor(mockType) == null) {
+        assert DesugaredLibraryAPIConverter.isVivifiedType(mockType);
+        assert wrapper.instanceFields().size() == 1;
+        DexType typeToMock = wrapper.instanceFields().get(0).field.type;
+        DexClass classToMock = appView.definitionFor(typeToMock);
+        assert classToMock != null;
+        DexClasspathClass mockedSuperClass =
+            converter.synthesizeClasspathMock(classToMock, mockType, mockIsInterface);
+        classpathClasses.add(mockedSuperClass);
+        for (DexEncodedMethod virtualMethod : wrapper.virtualMethods()) {
+          // The mock is generated at the end of the enqueuing phase, so we need to manually set the
+          // library override.
+          assert mockedSuperClass.lookupVirtualMethod(virtualMethod.method) != null;
+          virtualMethod.setLibraryMethodOverride(OptionalBool.TRUE);
+        }
+      }
+    }
+    return classpathClasses;
+  }
+
+  public DesugaredLibraryConversionWrapperAnalysis registerWrite(
+      DexProgramClass wrapper, Consumer<DexEncodedMethod> registration) {
+    registration.accept(getInitializer(wrapper));
+    return this;
+  }
+
+  public DesugaredLibraryConversionWrapperAnalysis registerReads(
+      DexProgramClass wrapper, Consumer<DexEncodedMethod> registration) {
+    // The field of each wrapper is read exclusively in all virtual methods and the reverse wrapper
+    // convert method.
+    for (DexEncodedMethod virtualMethod : wrapper.virtualMethods()) {
+      registration.accept(virtualMethod);
+    }
+    DexProgramClass reverseWrapper = wrappersToReverseMap.get(wrapper);
+    if (reverseWrapper != null) {
+      registration.accept(getConvertMethod(reverseWrapper));
+    }
+    return this;
+  }
+
+  private DexEncodedMethod getInitializer(DexProgramClass wrapper) {
+    DexEncodedMethod initializer =
+        wrapper.lookupDirectMethod(DexEncodedMethod::isInstanceInitializer);
+    assert initializer != null;
+    return initializer;
+  }
+
+  private DexEncodedMethod getConvertMethod(DexProgramClass wrapper) {
+    DexEncodedMethod convertMethod = wrapper.lookupDirectMethod(DexEncodedMethod::isStatic);
+    assert convertMethod != null;
+    assert convertMethod.method.name == appView.dexItemFactory().convertMethodName;
+    return convertMethod;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInvokeAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInvokeAnalysis.java
new file mode 100644
index 0000000..d0a39b3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInvokeAnalysis.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2020, 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.graph.analysis;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface EnqueuerInvokeAnalysis {
+
+  /**
+   * Each traceInvokeXX method is called when a corresponding invoke is found while tracing a live
+   * method.
+   */
+  void traceInvokeStatic(DexMethod invokedMethod, ProgramMethod context);
+
+  void traceInvokeDirect(DexMethod invokedMethod, ProgramMethod context);
+
+  void traceInvokeInterface(DexMethod invokedMethod, ProgramMethod context);
+
+  void traceInvokeSuper(DexMethod invokedMethod, ProgramMethod context);
+
+  void traceInvokeVirtual(DexMethod invokedMethod, ProgramMethod context);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index e2881ad..37a8dc6 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -95,9 +95,6 @@
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
       Inliner inliner) {
-    if (method.method.toSourceString().contains("proto2.BuilderWithReusedSettersTestClass")) {
-      System.out.println();
-    }
     strengthenCheckCastInstructions(code);
 
     ProtoInliningReasonStrategy inliningReasonStrategy =
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index c70ff33..007fd9f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -24,6 +24,14 @@
     return null;
   }
 
+  public boolean isSingleConstClassValue() {
+    return false;
+  }
+
+  public SingleConstClassValue asSingleConstClassValue() {
+    return null;
+  }
+
   public boolean isSingleEnumValue() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
index 887a171..80ca063 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
@@ -6,10 +6,13 @@
 
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
 import java.util.concurrent.ConcurrentHashMap;
 
 public class AbstractValueFactory {
 
+  private ConcurrentHashMap<DexType, SingleConstClassValue> singleConstClassValues =
+      new ConcurrentHashMap<>();
   private ConcurrentHashMap<DexField, SingleEnumValue> singleEnumValues = new ConcurrentHashMap<>();
   private ConcurrentHashMap<DexField, SingleFieldValue> singleFieldValues =
       new ConcurrentHashMap<>();
@@ -17,6 +20,10 @@
   private ConcurrentHashMap<DexString, SingleStringValue> singleStringValues =
       new ConcurrentHashMap<>();
 
+  public SingleConstClassValue createSingleConstClassValue(DexType type) {
+    return singleConstClassValues.computeIfAbsent(type, SingleConstClassValue::new);
+  }
+
   public SingleEnumValue createSingleEnumValue(DexField field) {
     return singleEnumValues.computeIfAbsent(field, SingleEnumValue::new);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
new file mode 100644
index 0000000..a6bf499
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
@@ -0,0 +1,91 @@
+// Copyright (c) 2020, 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.analysis.value;
+
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.classClassType;
+import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isClassTypeVisibleFromContext;
+
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.ConstClass;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
+import com.android.tools.r8.ir.code.Value;
+
+public class SingleConstClassValue extends SingleValue {
+
+  private final DexType type;
+
+  /** Intentionally package private, use {@link AbstractValueFactory} instead. */
+  SingleConstClassValue(DexType type) {
+    this.type = type;
+  }
+
+  @Override
+  public boolean isSingleConstClassValue() {
+    return true;
+  }
+
+  @Override
+  public SingleConstClassValue asSingleConstClassValue() {
+    return this;
+  }
+
+  public DexType getType() {
+    return type;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    return this == o;
+  }
+
+  @Override
+  public int hashCode() {
+    return type.hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return "SingleConstClassValue(" + type.toSourceString() + ")";
+  }
+
+  @Override
+  public Instruction createMaterializingInstruction(
+      AppView<? extends AppInfoWithSubtyping> appView, IRCode code, TypeAndLocalInfoSupplier info) {
+    TypeLatticeElement typeLattice = info.getTypeLattice();
+    DebugLocalInfo debugLocalInfo = info.getLocalInfo();
+    assert typeLattice.isClassType();
+    assert appView
+        .isSubtype(
+            appView.dexItemFactory().classType,
+            typeLattice.asClassTypeLatticeElement().getClassType())
+        .isTrue();
+    Value returnedValue =
+        code.createValue(classClassType(appView, definitelyNotNull()), debugLocalInfo);
+    ConstClass instruction = new ConstClass(returnedValue, type);
+    assert !instruction.instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return instruction;
+  }
+
+  @Override
+  public boolean isMaterializableInContext(AppView<?> appView, DexType context) {
+    DexType baseType = type.toBaseType(appView.dexItemFactory());
+    if (baseType.isClassType()) {
+      DexClass clazz = appView.definitionFor(type);
+      return clazz != null
+          && clazz.isResolvable(appView)
+          && isClassTypeVisibleFromContext(appView, context, clazz);
+    }
+    assert baseType.isPrimitiveType();
+    return true;
+  }
+}
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 4c3822a..f4bb790 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
@@ -527,6 +527,15 @@
     return !phis.isEmpty();
   }
 
+  public boolean hasDeadPhi(AppView<?> appView, IRCode code) {
+    for (Phi phi : phis) {
+      if (phi.isDead(appView, code)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   public List<Phi> getPhis() {
     return phis;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 3cae82a..98f4c97 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -223,8 +223,7 @@
   }
 
   @Override
-  public void replaceCurrentInstructionWithConstInt(
-      AppView<? extends AppInfoWithSubtyping> appView, IRCode code, int value) {
+  public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
     if (current == null) {
       throw new IllegalStateException();
     }
@@ -244,10 +243,7 @@
 
   @Override
   public void replaceCurrentInstructionWithStaticGet(
-      AppView<? extends AppInfoWithSubtyping> appView,
-      IRCode code,
-      DexField field,
-      Set<Value> affectedValues) {
+      AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues) {
     if (current == null) {
       throw new IllegalStateException();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index f58d657..4a58946 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -16,6 +16,8 @@
 import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -187,4 +189,12 @@
   public void buildCf(CfBuilder builder) {
     builder.add(new CfConstClass(clazz));
   }
+
+  @Override
+  public AbstractValue getAbstractValue(AppView<?> appView, DexType context) {
+    if (!instructionMayHaveSideEffects(appView, context)) {
+      return appView.abstractValueFactory().createSingleConstClassValue(context);
+    }
+    return UnknownValue.getInstance();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index f070e0f..ff88eed 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -38,17 +38,13 @@
   }
 
   @Override
-  public void replaceCurrentInstructionWithConstInt(
-      AppView<? extends AppInfoWithSubtyping> appView, IRCode code, int value) {
-    instructionIterator.replaceCurrentInstructionWithConstInt(appView, code, value);
+  public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
+    instructionIterator.replaceCurrentInstructionWithConstInt(code, value);
   }
 
   @Override
   public void replaceCurrentInstructionWithStaticGet(
-      AppView<? extends AppInfoWithSubtyping> appView,
-      IRCode code,
-      DexField field,
-      Set<Value> affectedValues) {
+      AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues) {
     instructionIterator.replaceCurrentInstructionWithStaticGet(
         appView, code, field, affectedValues);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index a8b539e..623da4d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -64,14 +64,10 @@
 
   Value insertConstIntInstruction(IRCode code, InternalOptions options, int value);
 
-  void replaceCurrentInstructionWithConstInt(
-      AppView<? extends AppInfoWithSubtyping> appView, IRCode code, int value);
+  void replaceCurrentInstructionWithConstInt(IRCode code, int value);
 
   void replaceCurrentInstructionWithStaticGet(
-      AppView<? extends AppInfoWithSubtyping> appView,
-      IRCode code,
-      DexField field,
-      Set<Value> affectedValues);
+      AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues);
 
   /**
    * Replace the current instruction with null throwing instructions.
@@ -81,10 +77,10 @@
    * @param blockIterator basic block iterator used to iterate the blocks.
    * @param blocksToRemove set passed where blocks that were detached from the graph, but not
    *     removed yet are added. When inserting `throw null`, catch handlers whose guard does not
-   *     catch NPE will be removed, but not yet removed using the passed block
-   *     <code>blockIterator</code>. When iterating using <code>blockIterator</code> after then
-   *     method returns the blocks in this set must be skipped when iterating with the active
-   *     <code>blockIterator</code> and ultimately removed.
+   *     catch NPE will be removed, but not yet removed using the passed block <code>blockIterator
+   *     </code>. When iterating using <code>blockIterator</code> after then method returns the
+   *     blocks in this set must be skipped when iterating with the active <code>blockIterator
+   *     </code> and ultimately removed.
    * @param affectedValues set passed where values depending on detached blocks will be added.
    */
   void replaceCurrentInstructionWithThrowNull(
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 451b9a5..7624412 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
@@ -83,21 +83,11 @@
     if (!isInvokeMethodWithDynamicDispatch() || !appView.appInfo().hasLiveness()) {
       return null;
     }
-    Collection<DexEncodedMethod> targets;
-    if (isInvokeVirtual()) {
-      targets =
-          appView
-              .appInfo()
-              .resolveMethodOnClass(method.holder, method)
-              .lookupVirtualTargets(appView.appInfo());
-    } else {
-      assert isInvokeInterface();
-      targets =
-          appView
-              .appInfo()
-              .resolveMethodOnInterface(method.holder, method)
-              .lookupInterfaceTargets(appView.appInfo());
-    }
+    Collection<DexEncodedMethod> targets =
+        appView
+            .appInfo()
+            .resolveMethod(method.holder, method)
+            .lookupVirtualDispatchTargets(appView.appInfo());
     if (targets == null) {
       return null;
     }
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 5d782c3..e1d3d75 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
@@ -8,6 +8,7 @@
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokeStaticRange;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -109,6 +110,11 @@
           result, appView.appInfo().lookupStaticTargetOnItself(invokedMethod, invocationContext));
       return result;
     }
+    // Allow optimizing static library invokes in D8.
+    DexClass clazz = appView.definitionFor(getInvokedMethod().holder);
+    if (clazz != null && clazz.isLibraryClass()) {
+      return appView.definitionFor(getInvokedMethod());
+    }
     // In D8, we can treat invoke-static instructions as having a single target if the invoke is
     // targeting a method in the enclosing class.
     return appView.appInfo().lookupStaticTargetOnItself(invokedMethod, invocationContext);
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 da5a5e6..bdc7ce4 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
@@ -8,6 +8,7 @@
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokeVirtualRange;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -99,6 +100,18 @@
           TypeAnalysis.getRefinedReceiverType(appViewWithLiveness, this),
           getReceiver().getDynamicLowerBoundType(appViewWithLiveness));
     }
+    // In D8, allow lookupSingleTarget() to be used for finding final library methods. This is used
+    // for library modeling.
+    DexType holder = getInvokedMethod().holder;
+    if (holder.isClassType()) {
+      DexClass clazz = appView.definitionFor(holder);
+      if (clazz != null && clazz.isLibraryClass()) {
+        DexEncodedMethod singleTargetCandidate = appView.definitionFor(getInvokedMethod());
+        if (singleTargetCandidate != null && (clazz.isFinal() || singleTargetCandidate.isFinal())) {
+          return singleTargetCandidate;
+        }
+      }
+    }
     return null;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index 6de9e58..2a82bcc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -51,17 +51,13 @@
   }
 
   @Override
-  public void replaceCurrentInstructionWithConstInt(
-      AppView<? extends AppInfoWithSubtyping> appView, IRCode code, int value) {
-    currentBlockIterator.replaceCurrentInstructionWithConstInt(appView, code, value);
+  public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
+    currentBlockIterator.replaceCurrentInstructionWithConstInt(code, value);
   }
 
   @Override
   public void replaceCurrentInstructionWithStaticGet(
-      AppView<? extends AppInfoWithSubtyping> appView,
-      IRCode code,
-      DexField field,
-      Set<Value> affectedValues) {
+      AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues) {
     currentBlockIterator.replaceCurrentInstructionWithStaticGet(
         appView, code, field, affectedValues);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 06f1f58..6f3bf92 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -47,6 +47,14 @@
     // Incoming calls to this method.
     private final Set<Node> callers = new TreeSet<>();
 
+    // Incoming field read edges to this method (i.e., the set of methods that read a field written
+    // by the current method).
+    private final Set<Node> readers = new TreeSet<>();
+
+    // Outgoing field read edges from this method (i.e., the set of methods that write a field read
+    // by the current method).
+    private final Set<Node> writers = new TreeSet<>();
+
     public Node(DexEncodedMethod method) {
       this.method = method;
     }
@@ -57,12 +65,17 @@
 
     public void addCallerConcurrently(Node caller, boolean likelySpuriousCallEdge) {
       if (caller != this && !likelySpuriousCallEdge) {
+        boolean changedCallers;
         synchronized (callers) {
-          callers.add(caller);
+          changedCallers = callers.add(caller);
           numberOfCallSites++;
         }
-        synchronized (caller.callees) {
-          caller.callees.add(this);
+        if (changedCallers) {
+          synchronized (caller.callees) {
+            caller.callees.add(this);
+          }
+          // Avoid redundant field read edges (call edges are considered stronger).
+          removeReaderConcurrently(caller);
         }
       } else {
         synchronized (callers) {
@@ -71,22 +84,74 @@
       }
     }
 
-    public void removeCaller(Node caller) {
-      callers.remove(caller);
-      caller.callees.remove(this);
-    }
-
-    public void cleanCalleesForRemoval() {
-      assert callers.isEmpty();
-      for (Node callee : callees) {
-        callee.callers.remove(this);
+    public void addReaderConcurrently(Node reader) {
+      if (reader != this) {
+        synchronized (callers) {
+          if (callers.contains(reader)) {
+            // Avoid redundant field read edges (call edges are considered stronger).
+            return;
+          }
+          boolean readersChanged;
+          synchronized (readers) {
+            readersChanged = readers.add(reader);
+          }
+          if (readersChanged) {
+            synchronized (reader.writers) {
+              reader.writers.add(this);
+            }
+          }
+        }
       }
     }
 
-    public void cleanCallersForRemoval() {
+    private void removeReaderConcurrently(Node reader) {
+      synchronized (readers) {
+        readers.remove(reader);
+      }
+      synchronized (reader.writers) {
+        reader.writers.remove(this);
+      }
+    }
+
+    public void removeCaller(Node caller) {
+      boolean callersChanged = callers.remove(caller);
+      assert callersChanged;
+      boolean calleesChanged = caller.callees.remove(this);
+      assert calleesChanged;
+      assert !hasReader(caller);
+    }
+
+    public void removeReader(Node reader) {
+      boolean readersChanged = readers.remove(reader);
+      assert readersChanged;
+      boolean writersChanged = reader.writers.remove(this);
+      assert writersChanged;
+      assert !hasCaller(reader);
+    }
+
+    public void cleanCalleesAndWritersForRemoval() {
+      assert callers.isEmpty();
+      assert readers.isEmpty();
+      for (Node callee : callees) {
+        boolean changed = callee.callers.remove(this);
+        assert changed;
+      }
+      for (Node writer : writers) {
+        boolean changed = writer.readers.remove(this);
+        assert changed;
+      }
+    }
+
+    public void cleanCallersAndReadersForRemoval() {
       assert callees.isEmpty();
+      assert writers.isEmpty();
       for (Node caller : callers) {
-        caller.callees.remove(this);
+        boolean changed = caller.callees.remove(this);
+        assert changed;
+      }
+      for (Node reader : readers) {
+        boolean changed = reader.writers.remove(this);
+        assert changed;
       }
     }
 
@@ -98,6 +163,14 @@
       return callees;
     }
 
+    public Set<Node> getReadersWithDeterministicOrder() {
+      return readers;
+    }
+
+    public Set<Node> getWritersWithDeterministicOrder() {
+      return writers;
+    }
+
     public int getNumberOfCallSites() {
       return numberOfCallSites;
     }
@@ -110,12 +183,20 @@
       return callers.contains(method);
     }
 
+    public boolean hasReader(Node method) {
+      return readers.contains(method);
+    }
+
+    public boolean hasWriter(Node method) {
+      return writers.contains(method);
+    }
+
     public boolean isRoot() {
-      return callers.isEmpty();
+      return callers.isEmpty() && readers.isEmpty();
     }
 
     public boolean isLeaf() {
-      return callees.isEmpty();
+      return callees.isEmpty() && writers.isEmpty();
     }
 
     @Override
@@ -161,6 +242,10 @@
   final Set<Node> nodes;
   final CycleEliminationResult cycleEliminationResult;
 
+  CallGraph(Set<Node> nodes) {
+    this(nodes, null);
+  }
+
   CallGraph(Set<Node> nodes, CycleEliminationResult cycleEliminationResult) {
     this.nodes = nodes;
     this.cycleEliminationResult = cycleEliminationResult;
@@ -182,11 +267,11 @@
   }
 
   public Set<DexEncodedMethod> extractLeaves() {
-    return extractNodes(Node::isLeaf, Node::cleanCallersForRemoval);
+    return extractNodes(Node::isLeaf, Node::cleanCallersAndReadersForRemoval);
   }
 
   public Set<DexEncodedMethod> extractRoots() {
-    return extractNodes(Node::isRoot, Node::cleanCalleesForRemoval);
+    return extractNodes(Node::isRoot, Node::cleanCalleesAndWritersForRemoval);
   }
 
   private Set<DexEncodedMethod> extractNodes(Predicate<Node> predicate, Consumer<Node> clean) {
@@ -202,6 +287,7 @@
       }
     }
     removed.forEach(clean);
+    assert !result.isEmpty();
     return result;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
index 42842a2..5b94efb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
@@ -20,7 +20,7 @@
   }
 
   @Override
-  void process(ExecutorService executorService) throws ExecutionException {
+  void populateGraph(ExecutorService executorService) throws ExecutionException {
     ThreadUtils.processItems(appView.appInfo().classes(), this::processClass, executorService);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
index ade31ca..610240e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
@@ -11,7 +13,10 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessInfo;
+import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
@@ -20,20 +25,18 @@
 import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator.CycleEliminationResult;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterators;
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Deque;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.LinkedList;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -43,17 +46,24 @@
 import java.util.function.Predicate;
 
 abstract class CallGraphBuilderBase {
+
   final AppView<AppInfoWithLiveness> appView;
+  private final FieldAccessInfoCollection<?> fieldAccessInfoCollection;
   final Map<DexMethod, Node> nodes = new IdentityHashMap<>();
   private final Map<DexMethod, Set<DexEncodedMethod>> possibleTargetsCache =
       new ConcurrentHashMap<>();
 
   CallGraphBuilderBase(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
+    this.fieldAccessInfoCollection = appView.appInfo().getFieldAccessInfoCollection();
   }
 
   public CallGraph build(ExecutorService executorService, Timing timing) throws ExecutionException {
-    process(executorService);
+    timing.begin("Build IR processing order constraints");
+    timing.begin("Build call graph");
+    populateGraph(executorService);
+    assert verifyNoRedundantFieldReadEdges();
+    timing.end();
     assert verifyAllMethodsWithCodeExists();
 
     appView.withGeneratedMessageLiteBuilderShrinker(
@@ -62,16 +72,28 @@
     timing.begin("Cycle elimination");
     // Sort the nodes for deterministic cycle elimination.
     Set<Node> nodesWithDeterministicOrder = Sets.newTreeSet(nodes.values());
-    CycleEliminator cycleEliminator =
-        new CycleEliminator(nodesWithDeterministicOrder, appView.options());
-    CycleEliminationResult cycleEliminationResult = cycleEliminator.breakCycles();
+    CycleEliminator cycleEliminator = new CycleEliminator();
+    CycleEliminationResult cycleEliminationResult =
+        cycleEliminator.breakCycles(nodesWithDeterministicOrder);
     timing.end();
-    assert cycleEliminator.breakCycles().numberOfRemovedEdges() == 0; // The cycles should be gone.
+    timing.end();
+    assert cycleEliminator.breakCycles(nodesWithDeterministicOrder).numberOfRemovedCallEdges()
+        == 0; // The cycles should be gone.
 
     return new CallGraph(nodesWithDeterministicOrder, cycleEliminationResult);
   }
 
-  abstract void process(ExecutorService executorService) throws ExecutionException;
+  abstract void populateGraph(ExecutorService executorService) throws ExecutionException;
+
+  /** Verify that there are no field read edges in the graph if there is also a call graph edge. */
+  private boolean verifyNoRedundantFieldReadEdges() {
+    for (Node writer : nodes.values()) {
+      for (Node reader : writer.getReadersWithDeterministicOrder()) {
+        assert !writer.hasCaller(reader);
+      }
+    }
+    return true;
+  }
 
   Node getOrCreateNode(DexEncodedMethod method) {
     synchronized (nodes) {
@@ -83,31 +105,31 @@
 
   class InvokeExtractor extends UseRegistry {
 
-    private final Node caller;
+    private final Node currentMethod;
     private final Predicate<DexEncodedMethod> targetTester;
 
-    InvokeExtractor(Node caller, Predicate<DexEncodedMethod> targetTester) {
+    InvokeExtractor(Node currentMethod, Predicate<DexEncodedMethod> targetTester) {
       super(appView.dexItemFactory());
-      this.caller = caller;
+      this.currentMethod = currentMethod;
       this.targetTester = targetTester;
     }
 
-    private void addClassInitializerTarget(DexClass clazz) {
+    private void addClassInitializerTarget(DexProgramClass clazz) {
       assert clazz != null;
-      if (clazz.isProgramClass() && clazz.hasClassInitializer()) {
-        addTarget(clazz.getClassInitializer(), false);
+      if (clazz.hasClassInitializer()) {
+        addCallEdge(clazz.getClassInitializer(), false);
       }
     }
 
     private void addClassInitializerTarget(DexType type) {
       assert type.isClassType();
-      DexClass clazz = appView.definitionFor(type);
+      DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
       if (clazz != null) {
         addClassInitializerTarget(clazz);
       }
     }
 
-    private void addTarget(DexEncodedMethod callee, boolean likelySpuriousCallEdge) {
+    private void addCallEdge(DexEncodedMethod callee, boolean likelySpuriousCallEdge) {
       if (!targetTester.test(callee)) {
         return;
       }
@@ -122,11 +144,20 @@
         return;
       }
       assert callee.isProgramMethod(appView);
-      getOrCreateNode(callee).addCallerConcurrently(caller, likelySpuriousCallEdge);
+      getOrCreateNode(callee).addCallerConcurrently(currentMethod, likelySpuriousCallEdge);
+    }
+
+    private void addFieldReadEdge(DexEncodedMethod writer) {
+      assert !writer.accessFlags.isAbstract();
+      if (!targetTester.test(writer)) {
+        return;
+      }
+      assert writer.isProgramMethod(appView);
+      getOrCreateNode(writer).addReaderConcurrently(currentMethod);
     }
 
     private void processInvoke(Invoke.Type originalType, DexMethod originalMethod) {
-      DexEncodedMethod source = caller.method;
+      DexEncodedMethod source = currentMethod.method;
       DexMethod context = source.method;
       GraphLenseLookupResult result =
           appView.graphLense().lookupMethod(originalMethod, context, originalType);
@@ -143,15 +174,15 @@
         DexEncodedMethod singleTarget =
             appView.appInfo().lookupSingleTarget(type, method, context.holder);
         if (singleTarget != null) {
-          assert !source.accessFlags.isBridge() || singleTarget != caller.method;
-          DexClass clazz = appView.definitionFor(singleTarget.method.holder);
-          assert clazz != null;
-          if (clazz.isProgramClass()) {
+          assert !source.accessFlags.isBridge() || singleTarget != currentMethod.method;
+          DexProgramClass clazz =
+              asProgramClassOrNull(appView.definitionFor(singleTarget.method.holder));
+          if (clazz != null) {
             // For static invokes, the class could be initialized.
             if (type == Invoke.Type.STATIC) {
               addClassInitializerTarget(clazz);
             }
-            addTarget(singleTarget, false);
+            addCallEdge(singleTarget, false);
           }
         }
       }
@@ -181,7 +212,7 @@
                 ResolutionResult resolution =
                     appView.appInfo().resolveMethod(method.holder, method, isInterface);
                 if (resolution.isVirtualTarget()) {
-                  return resolution.lookupVirtualDispatchTargets(isInterface, appView.appInfo());
+                  return resolution.lookupVirtualDispatchTargets(appView.appInfo());
                 }
                 return null;
               });
@@ -190,17 +221,46 @@
             possibleTargets.size() >= appView.options().callGraphLikelySpuriousCallEdgeThreshold;
         for (DexEncodedMethod possibleTarget : possibleTargets) {
           if (possibleTarget.isProgramMethod(appView)) {
-            addTarget(possibleTarget, likelySpuriousCallEdge);
+            addCallEdge(possibleTarget, likelySpuriousCallEdge);
           }
         }
       }
     }
 
-    private void processFieldAccess(DexField field) {
-      // Any field access implicitly calls the class initializer.
+    private void processFieldRead(DexField field) {
+      if (!field.holder.isClassType()) {
+        return;
+      }
+
+      DexEncodedField encodedField = appView.appInfo().resolveField(field);
+      if (encodedField == null || appView.appInfo().isPinned(encodedField.field)) {
+        return;
+      }
+
+      DexProgramClass clazz =
+          asProgramClassOrNull(appView.definitionFor(encodedField.field.holder));
+      if (clazz == null) {
+        return;
+      }
+
+      // Each static field access implicitly triggers the class initializer.
+      if (encodedField.isStatic()) {
+        addClassInitializerTarget(clazz);
+      }
+
+      FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(encodedField.field);
+      if (fieldAccessInfo != null) {
+        if (fieldAccessInfo.getNumberOfWriteContexts() == 1) {
+          fieldAccessInfo.forEachWriteContext(this::addFieldReadEdge);
+        }
+      }
+    }
+
+    private void processFieldWrite(DexField field) {
       if (field.holder.isClassType()) {
         DexEncodedField encodedField = appView.appInfo().resolveField(field);
         if (encodedField != null && encodedField.isStatic()) {
+          // Each static field access implicitly triggers the class initializer.
           addClassInitializerTarget(field.holder);
         }
       }
@@ -237,14 +297,14 @@
     }
 
     @Override
-    public boolean registerInstanceFieldWrite(DexField field) {
-      processFieldAccess(field);
+    public boolean registerInstanceFieldRead(DexField field) {
+      processFieldRead(field);
       return false;
     }
 
     @Override
-    public boolean registerInstanceFieldRead(DexField field) {
-      processFieldAccess(field);
+    public boolean registerInstanceFieldWrite(DexField field) {
+      processFieldWrite(field);
       return false;
     }
 
@@ -258,13 +318,13 @@
 
     @Override
     public boolean registerStaticFieldRead(DexField field) {
-      processFieldAccess(field);
+      processFieldRead(field);
       return false;
     }
 
     @Override
     public boolean registerStaticFieldWrite(DexField field) {
-      processFieldAccess(field);
+      processFieldWrite(field);
       return false;
     }
 
@@ -294,79 +354,108 @@
         this.caller = caller;
         this.callee = callee;
       }
+    }
 
-      public void remove() {
-        callee.removeCaller(caller);
+    static class StackEntryInfo {
+
+      final int index;
+      final Node predecessor;
+
+      boolean processed;
+
+      StackEntryInfo(int index, Node predecessor) {
+        this.index = index;
+        this.predecessor = predecessor;
       }
     }
 
     static class CycleEliminationResult {
 
-      private Map<Node, Set<Node>> removedEdges;
+      private Map<DexEncodedMethod, Set<DexEncodedMethod>> removedCallEdges;
 
-      CycleEliminationResult(Map<Node, Set<Node>> removedEdges) {
-        this.removedEdges = removedEdges;
+      CycleEliminationResult(Map<DexEncodedMethod, Set<DexEncodedMethod>> removedCallEdges) {
+        this.removedCallEdges = removedCallEdges;
       }
 
-      void forEachRemovedCaller(Node callee, Consumer<Node> fn) {
-        removedEdges.getOrDefault(callee, ImmutableSet.of()).forEach(fn);
+      void forEachRemovedCaller(DexEncodedMethod callee, Consumer<DexEncodedMethod> fn) {
+        removedCallEdges.getOrDefault(callee, ImmutableSet.of()).forEach(fn);
       }
 
-      int numberOfRemovedEdges() {
-        int numberOfRemovedEdges = 0;
-        for (Set<Node> nodes : removedEdges.values()) {
-          numberOfRemovedEdges += nodes.size();
+      int numberOfRemovedCallEdges() {
+        int numberOfRemovedCallEdges = 0;
+        for (Set<DexEncodedMethod> nodes : removedCallEdges.values()) {
+          numberOfRemovedCallEdges += nodes.size();
         }
-        return numberOfRemovedEdges;
+        return numberOfRemovedCallEdges;
       }
     }
 
-    private final Collection<Node> nodes;
-    private final InternalOptions options;
-
     // DFS stack.
     private Deque<Node> stack = new ArrayDeque<>();
 
-    // Set of nodes on the DFS stack.
-    private Set<Node> stackSet = Sets.newIdentityHashSet();
+    // Nodes on the DFS stack.
+    private Map<Node, StackEntryInfo> stackEntryInfo = new IdentityHashMap<>();
+
+    // Subset of the DFS stack, where the nodes on the stack satisfy that the edge from the
+    // predecessor to the node itself is a field read edge.
+    //
+    // This stack is used to efficiently compute if there is a field read edge inside a cycle when
+    // a cycle is found.
+    private Deque<Node> writerStack = new ArrayDeque<>();
 
     // Set of nodes that have been visited entirely.
     private Set<Node> marked = Sets.newIdentityHashSet();
 
+    // Call edges that should be removed when the caller has been processed. These are not removed
+    // directly since that would lead to ConcurrentModificationExceptions.
+    private Map<Node, Set<Node>> calleesToBeRemoved = new IdentityHashMap<>();
+
+    // Field read edges that should be removed when the reader has been processed. These are not
+    // removed directly since that would lead to ConcurrentModificationExceptions.
+    private Map<Node, Set<Node>> writersToBeRemoved = new IdentityHashMap<>();
+
     // Mapping from callee to the set of callers that were removed from the callee.
-    private Map<Node, Set<Node>> removedEdges = new IdentityHashMap<>();
+    private Map<DexEncodedMethod, Set<DexEncodedMethod>> removedCallEdges = new IdentityHashMap<>();
 
-    private int maxDepth = 0;
+    // Set of nodes from which cycle elimination must be rerun to ensure that all cycles will be
+    // removed.
+    private LinkedHashSet<Node> revisit = new LinkedHashSet<>();
 
-    CycleEliminator(Collection<Node> nodes, InternalOptions options) {
-      this.options = options;
+    CycleEliminationResult breakCycles(Collection<Node> roots) {
+      // Break cycles in this call graph by removing edges causing cycles. We do this in a fixpoint
+      // because the algorithm does not guarantee that all cycles will be removed from the graph
+      // when we remove an edge in the middle of a cycle that contains another cycle.
+      do {
+        traverse(roots);
+        roots = revisit;
+        prepareForNewTraversal();
+      } while (!roots.isEmpty());
 
-      // Call to reorderNodes must happen after assigning options.
-      this.nodes =
-          options.testing.nondeterministicCycleElimination
-              ? reorderNodes(new ArrayList<>(nodes))
-              : nodes;
-    }
-
-    CycleEliminationResult breakCycles() {
-      // Break cycles in this call graph by removing edges causing cycles.
-      traverse();
-
-      CycleEliminationResult result = new CycleEliminationResult(removedEdges);
+      CycleEliminationResult result = new CycleEliminationResult(removedCallEdges);
       if (Log.ENABLED) {
-        Log.info(getClass(), "# call graph cycles broken: %s", result.numberOfRemovedEdges());
-        Log.info(getClass(), "# max call graph depth: %s", maxDepth);
+        Log.info(getClass(), "# call graph cycles broken: %s", result.numberOfRemovedCallEdges());
       }
       reset();
       return result;
     }
 
-    private void reset() {
+    private void prepareForNewTraversal() {
+      assert calleesToBeRemoved.isEmpty();
       assert stack.isEmpty();
-      assert stackSet.isEmpty();
+      assert stackEntryInfo.isEmpty();
+      assert writersToBeRemoved.isEmpty();
+      assert writerStack.isEmpty();
       marked.clear();
-      maxDepth = 0;
-      removedEdges = new IdentityHashMap<>();
+      revisit = new LinkedHashSet<>();
+    }
+
+    private void reset() {
+      assert marked.isEmpty();
+      assert revisit.isEmpty();
+      assert stack.isEmpty();
+      assert stackEntryInfo.isEmpty();
+      assert writerStack.isEmpty();
+      removedCallEdges = new IdentityHashMap<>();
     }
 
     private static class WorkItem {
@@ -406,12 +495,12 @@
     }
 
     private static class IteratorWorkItem extends WorkItem {
-      private final Node caller;
-      private final Iterator<Node> callees;
+      private final Node callerOrReader;
+      private final Iterator<Node> calleesAndWriters;
 
-      IteratorWorkItem(Node caller, Iterator<Node> callees) {
-        this.caller = caller;
-        this.callees = callees;
+      IteratorWorkItem(Node callerOrReader, Iterator<Node> calleesAndWriters) {
+        this.callerOrReader = callerOrReader;
+        this.calleesAndWriters = calleesAndWriters;
       }
 
       @Override
@@ -425,134 +514,164 @@
       }
     }
 
-    private void traverse() {
-      Deque<WorkItem> workItems = new ArrayDeque<>(nodes.size());
-      for (Node node : nodes) {
+    private void traverse(Collection<Node> roots) {
+      Deque<WorkItem> workItems = new ArrayDeque<>(roots.size());
+      for (Node node : roots) {
         workItems.addLast(new NodeWorkItem(node));
       }
       while (!workItems.isEmpty()) {
         WorkItem workItem = workItems.removeFirst();
         if (workItem.isNode()) {
           Node node = workItem.asNode().node;
-          if (Log.ENABLED) {
-            if (stack.size() > maxDepth) {
-              maxDepth = stack.size();
-            }
-          }
-
           if (marked.contains(node)) {
             // Already visited all nodes that can be reached from this node.
             continue;
           }
 
-          push(node);
+          Node predecessor = stack.isEmpty() ? null : stack.peek();
+          push(node, predecessor);
 
-          // The callees must be sorted before calling traverse recursively. This ensures that
-          // cycles are broken the same way across multiple compilations.
-          Collection<Node> callees = node.getCalleesWithDeterministicOrder();
-
-          if (options.testing.nondeterministicCycleElimination) {
-            callees = reorderNodes(new ArrayList<>(callees));
-          }
-          workItems.addFirst(new IteratorWorkItem(node, callees.iterator()));
+          // The callees and writers must be sorted before calling traverse recursively.
+          // This ensures that cycles are broken the same way across multiple compilations.
+          Iterator<Node> calleesAndWriterIterator =
+              Iterators.concat(
+                  node.getCalleesWithDeterministicOrder().iterator(),
+                  node.getWritersWithDeterministicOrder().iterator());
+          workItems.addFirst(new IteratorWorkItem(node, calleesAndWriterIterator));
         } else {
           assert workItem.isIterator();
           IteratorWorkItem iteratorWorkItem = workItem.asIterator();
-          Node newCaller = iterateCallees(iteratorWorkItem.callees, iteratorWorkItem.caller);
-          if (newCaller != null) {
+          Node newCallerOrReader =
+              iterateCalleesAndWriters(
+                  iteratorWorkItem.calleesAndWriters, iteratorWorkItem.callerOrReader);
+          if (newCallerOrReader != null) {
             // We did not finish the work on this iterator, so add it again.
             workItems.addFirst(iteratorWorkItem);
-            workItems.addFirst(new NodeWorkItem(newCaller));
+            workItems.addFirst(new NodeWorkItem(newCallerOrReader));
           } else {
-            assert !iteratorWorkItem.callees.hasNext();
-            pop(iteratorWorkItem.caller);
-            marked.add(iteratorWorkItem.caller);
+            assert !iteratorWorkItem.calleesAndWriters.hasNext();
+            pop(iteratorWorkItem.callerOrReader);
+            marked.add(iteratorWorkItem.callerOrReader);
+
+            Collection<Node> calleesToBeRemovedFromCaller =
+                calleesToBeRemoved.remove(iteratorWorkItem.callerOrReader);
+            if (calleesToBeRemovedFromCaller != null) {
+              calleesToBeRemovedFromCaller.forEach(
+                  callee -> {
+                    callee.removeCaller(iteratorWorkItem.callerOrReader);
+                    recordCallEdgeRemoval(iteratorWorkItem.callerOrReader, callee);
+                  });
+            }
+
+            Collection<Node> writersToBeRemovedFromReader =
+                writersToBeRemoved.remove(iteratorWorkItem.callerOrReader);
+            if (writersToBeRemovedFromReader != null) {
+              writersToBeRemovedFromReader.forEach(
+                  writer -> writer.removeReader(iteratorWorkItem.callerOrReader));
+            }
           }
         }
       }
     }
 
-    private Node iterateCallees(Iterator<Node> calleeIterator, Node node) {
-      while (calleeIterator.hasNext()) {
-        Node callee = calleeIterator.next();
-        boolean foundCycle = stackSet.contains(callee);
-        if (foundCycle) {
-          // Found a cycle that needs to be eliminated.
-          if (edgeRemovalIsSafe(node, callee)) {
-            // Break the cycle by removing the edge node->callee.
-            if (options.testing.nondeterministicCycleElimination) {
-              callee.removeCaller(node);
-            } else {
-              // Need to remove `callee` from `node.callees` using the iterator to prevent a
-              // ConcurrentModificationException. This is not needed when nondeterministic cycle
-              // elimination is enabled, because we iterate a copy of `node.callees` in that case.
-              calleeIterator.remove();
-              callee.getCallersWithDeterministicOrder().remove(node);
-            }
-            recordEdgeRemoval(node, callee);
-
-            if (Log.ENABLED) {
-              Log.info(
-                  CallGraph.class,
-                  "Removed call edge from method '%s' to '%s'",
-                  node.method.toSourceString(),
-                  callee.method.toSourceString());
-            }
-          } else {
-            assert foundCycle;
-
-            // The cycle has a method that is marked as force inline.
-            LinkedList<Node> cycle = extractCycle(callee);
-
-            if (Log.ENABLED) {
-              Log.info(
-                  CallGraph.class, "Extracted cycle to find an edge that can safely be removed");
-            }
-
-            // Break the cycle by finding an edge that can be removed without breaking force
-            // inlining. If that is not possible, this call fails with a compilation error.
-            CallEdge edge = findCallEdgeForRemoval(cycle);
-
-            // The edge will be null if this cycle has already been eliminated as a result of
-            // another cycle elimination.
-            if (edge != null) {
-              assert edgeRemovalIsSafe(edge.caller, edge.callee);
-
-              // Break the cycle by removing the edge caller->callee.
-              edge.remove();
-              recordEdgeRemoval(edge.caller, edge.callee);
-
-              if (Log.ENABLED) {
-                Log.info(
-                    CallGraph.class,
-                    "Removed call edge from force inlined method '%s' to '%s' to ensure that "
-                        + "force inlining will succeed",
-                    node.method.toSourceString(),
-                    callee.method.toSourceString());
-              }
-            }
-
-            // Recover the stack.
-            recoverStack(cycle);
-          }
-        } else {
-          return callee;
+    private Node iterateCalleesAndWriters(
+        Iterator<Node> calleeOrWriterIterator, Node callerOrReader) {
+      while (calleeOrWriterIterator.hasNext()) {
+        Node calleeOrWriter = calleeOrWriterIterator.next();
+        StackEntryInfo calleeOrWriterStackEntryInfo = stackEntryInfo.get(calleeOrWriter);
+        boolean foundCycle = calleeOrWriterStackEntryInfo != null;
+        if (!foundCycle) {
+          return calleeOrWriter;
         }
+
+        // Found a cycle that needs to be eliminated. If it is a field read edge, then remove it
+        // right away.
+        boolean isFieldReadEdge = calleeOrWriter.hasReader(callerOrReader);
+        if (isFieldReadEdge) {
+          removeFieldReadEdge(callerOrReader, calleeOrWriter);
+          continue;
+        }
+
+        // Otherwise, it is a call edge. Check if there is a field read edge in the cycle, and if
+        // so, remove that edge.
+        if (!writerStack.isEmpty()) {
+          Node lastKnownWriter = writerStack.peek();
+          StackEntryInfo lastKnownWriterStackEntryInfo = stackEntryInfo.get(lastKnownWriter);
+          boolean cycleContainsLastKnownWriter =
+              lastKnownWriterStackEntryInfo.index > calleeOrWriterStackEntryInfo.index;
+          if (cycleContainsLastKnownWriter) {
+            assert verifyCycleSatisfies(
+                calleeOrWriter,
+                cycle ->
+                    cycle.contains(lastKnownWriter)
+                        && cycle.contains(lastKnownWriterStackEntryInfo.predecessor));
+            if (!lastKnownWriterStackEntryInfo.processed) {
+              removeFieldReadEdge(lastKnownWriterStackEntryInfo.predecessor, lastKnownWriter);
+              revisit.add(lastKnownWriter);
+              lastKnownWriterStackEntryInfo.processed = true;
+            }
+            continue;
+          }
+        }
+
+        // It is a call edge, and the cycle does not contain any field read edges. In this case, we
+        // remove the call edge if it is safe according to force inlining.
+        if (callEdgeRemovalIsSafe(callerOrReader, calleeOrWriter)) {
+          // Break the cycle by removing the edge node->calleeOrWriter.
+          // Need to remove `calleeOrWriter` from `node.callees` using the iterator to prevent a
+          // ConcurrentModificationException.
+          removeCallEdge(callerOrReader, calleeOrWriter);
+          continue;
+        }
+
+        // The call edge cannot be removed due to force inlining. Find another call edge in the
+        // cycle that can safely be removed instead.
+        LinkedList<Node> cycle = extractCycle(calleeOrWriter);
+
+        // Break the cycle by finding an edge that can be removed without breaking force
+        // inlining. If that is not possible, this call fails with a compilation error.
+        CallEdge edge = findCallEdgeForRemoval(cycle);
+
+        // The edge will be null if this cycle has already been eliminated as a result of
+        // another cycle elimination.
+        if (edge != null) {
+          assert callEdgeRemovalIsSafe(edge.caller, edge.callee);
+
+          // Break the cycle by removing the edge caller->callee.
+          removeCallEdge(edge.caller, edge.callee);
+        }
+
+        // Recover the stack.
+        recoverStack(cycle);
       }
       return null;
     }
 
-    private void push(Node node) {
+    private void push(Node node, Node predecessor) {
       stack.push(node);
-      boolean changed = stackSet.add(node);
-      assert changed;
+      assert !stackEntryInfo.containsKey(node);
+      stackEntryInfo.put(node, new StackEntryInfo(stack.size() - 1, predecessor));
+      if (predecessor != null && predecessor.getWritersWithDeterministicOrder().contains(node)) {
+        writerStack.push(node);
+      }
     }
 
     private void pop(Node node) {
       Node popped = stack.pop();
       assert popped == node;
-      boolean changed = stackSet.remove(node);
-      assert changed;
+      assert stackEntryInfo.containsKey(node);
+      stackEntryInfo.remove(node);
+      if (writerStack.peek() == popped) {
+        writerStack.pop();
+      }
+    }
+
+    private void removeCallEdge(Node caller, Node callee) {
+      calleesToBeRemoved.computeIfAbsent(caller, ignore -> Sets.newIdentityHashSet()).add(callee);
+    }
+
+    private void removeFieldReadEdge(Node reader, Node writer) {
+      writersToBeRemoved.computeIfAbsent(reader, ignore -> Sets.newIdentityHashSet()).add(writer);
     }
 
     private LinkedList<Node> extractCycle(Node entry) {
@@ -564,15 +683,29 @@
       return cycle;
     }
 
+    private boolean verifyCycleSatisfies(Node entry, Predicate<LinkedList<Node>> predicate) {
+      LinkedList<Node> cycle = extractCycle(entry);
+      assert predicate.test(cycle);
+      recoverStack(cycle);
+      return true;
+    }
+
     private CallEdge findCallEdgeForRemoval(LinkedList<Node> extractedCycle) {
       Node callee = extractedCycle.getLast();
       for (Node caller : extractedCycle) {
+        if (caller.hasWriter(callee)) {
+          // Not a call edge.
+          assert !caller.hasCallee(callee);
+          assert !callee.hasCaller(caller);
+          callee = caller;
+          continue;
+        }
         if (!caller.hasCallee(callee)) {
           // No need to break any edges since this cycle has already been broken previously.
           assert !callee.hasCaller(caller);
           return null;
         }
-        if (edgeRemovalIsSafe(caller, callee)) {
+        if (callEdgeRemovalIsSafe(caller, callee)) {
           return new CallEdge(caller, callee);
         }
         callee = caller;
@@ -580,14 +713,17 @@
       throw new CompilationError(CYCLIC_FORCE_INLINING_MESSAGE);
     }
 
-    private static boolean edgeRemovalIsSafe(Node caller, Node callee) {
+    private static boolean callEdgeRemovalIsSafe(Node callerOrReader, Node calleeOrWriter) {
       // All call edges where the callee is a method that should be force inlined must be kept,
       // to guarantee that the IR converter will process the callee before the caller.
-      return !callee.method.getOptimizationInfo().forceInline();
+      assert calleeOrWriter.hasCaller(callerOrReader);
+      return !calleeOrWriter.method.getOptimizationInfo().forceInline();
     }
 
-    private void recordEdgeRemoval(Node caller, Node callee) {
-      removedEdges.computeIfAbsent(callee, ignore -> SetUtils.newIdentityHashSet(2)).add(caller);
+    private void recordCallEdgeRemoval(Node caller, Node callee) {
+      removedCallEdges
+          .computeIfAbsent(callee.method, ignore -> SetUtils.newIdentityHashSet(2))
+          .add(caller.method);
     }
 
     private void recoverStack(LinkedList<Node> extractedCycle) {
@@ -596,13 +732,5 @@
         stack.push(descendingIt.next());
       }
     }
-
-    private Collection<Node> reorderNodes(List<Node> nodes) {
-      assert options.testing.nondeterministicCycleElimination;
-      if (!InternalOptions.DETERMINISTIC_DEBUGGING) {
-        Collections.shuffle(nodes);
-      }
-      return nodes;
-    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 83ffaeb..a16d39d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -46,6 +46,7 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover;
 import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
 import com.android.tools.r8.ir.optimize.PhiOptimizations;
 import com.android.tools.r8.ir.optimize.peepholes.BasicBlockMuncher;
@@ -127,11 +128,11 @@
     this.code = code;
   }
 
-  public CfCode build(CodeRewriter rewriter) {
+  public CfCode build(DeadCodeRemover deadCodeRemover) {
     computeInitializers();
     TypeVerificationHelper typeVerificationHelper = new TypeVerificationHelper(appView, code);
     typeVerificationHelper.computeVerificationTypes();
-    rewriter.converter.deadCodeRemover.run(code);
+    assert deadCodeRemover.verifyNoDeadCode(code);
     rewriteNots();
     LoadStoreHelper loadStoreHelper = new LoadStoreHelper(appView, code, typeVerificationHelper);
     loadStoreHelper.insertLoadsAndStores();
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 67a0178..b1b6e81 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
@@ -42,6 +42,7 @@
 import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
 import com.android.tools.r8.ir.desugar.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.Mode;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor;
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
@@ -224,7 +225,8 @@
               ? null
               : new InterfaceMethodRewriter(appView, this);
       this.lambdaRewriter = new LambdaRewriter(appView);
-      this.desugaredLibraryAPIConverter = new DesugaredLibraryAPIConverter(appView);
+      this.desugaredLibraryAPIConverter =
+          new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS);
       this.twrCloseResourceRewriter = null;
       this.lambdaMerger = null;
       this.covariantReturnTypeAnnotationTransformer = null;
@@ -266,14 +268,13 @@
         options.processCovariantReturnTypeAnnotations
             ? new CovariantReturnTypeAnnotationTransformer(this, appView.dexItemFactory())
             : null;
+    this.libraryMethodOptimizer = new LibraryMethodOptimizer(appView);
     if (options.testing.forceAssumeNoneInsertion) {
       assumers.add(new AliasIntroducer(appView));
     }
     if (options.enableNonNullTracking) {
       assumers.add(new NonNullTracker(appView));
     }
-    this.desugaredLibraryAPIConverter =
-        appView.rewritePrefix.isRewriting() ? new DesugaredLibraryAPIConverter(appView) : null;
     if (appView.enableWholeProgramOptimizations()) {
       assert appView.appInfo().hasLiveness();
       assert appView.rootSet() != null;
@@ -291,7 +292,6 @@
       }
       this.fieldAccessAnalysis =
           FieldAccessAnalysis.enable(options) ? new FieldAccessAnalysis(appViewWithLiveness) : null;
-      this.libraryMethodOptimizer = new LibraryMethodOptimizer(appViewWithLiveness);
       this.libraryMethodOverrideAnalysis =
           options.enableTreeShakingOfLibraryMethodOverrides
               ? new LibraryMethodOverrideAnalysis(appViewWithLiveness)
@@ -323,13 +323,17 @@
           options.enableServiceLoaderRewriting
               ? new ServiceLoaderRewriter(appViewWithLiveness)
               : null;
+      this.desugaredLibraryAPIConverter =
+          appView.rewritePrefix.isRewriting()
+              ? new DesugaredLibraryAPIConverter(
+                  appView, Mode.ASSERT_CALLBACKS_AND_WRAPPERS_GENERATED)
+              : null;
       this.enumUnboxer = options.enableEnumUnboxing ? new EnumUnboxer(appViewWithLiveness) : null;
     } else {
       this.classInliner = null;
       this.classStaticizer = null;
       this.dynamicTypeOptimization = null;
       this.fieldAccessAnalysis = null;
-      this.libraryMethodOptimizer = null;
       this.libraryMethodOverrideAnalysis = null;
       this.inliner = null;
       this.lambdaMerger = null;
@@ -342,6 +346,10 @@
       this.typeChecker = null;
       this.d8NestBasedAccessDesugaring =
           options.shouldDesugarNests() ? new D8NestBasedAccessDesugaring(appView) : null;
+      this.desugaredLibraryAPIConverter =
+          appView.rewritePrefix.isRewriting()
+              ? new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS)
+              : null;
       this.serviceLoaderRewriter = null;
       this.methodOptimizationInfoCollector = null;
       this.enumUnboxer = null;
@@ -637,7 +645,7 @@
     PostMethodProcessor.Builder postMethodProcessorBuilder =
         new PostMethodProcessor.Builder(getOptimizationsForPostIRProcessing());
     {
-      timing.begin("Build call graph");
+      timing.begin("Build primary method processor");
       PrimaryMethodProcessor primaryMethodProcessor =
           PrimaryMethodProcessor.create(
               appView.withLiveness(), postMethodProcessorBuilder, executorService, timing);
@@ -747,7 +755,7 @@
             code -> {
               outliner.applyOutliningCandidate(code);
               printMethod(code, "IR after outlining (SSA)", null);
-              finalizeIR(
+              removeDeadCodeAndFinalizeIR(
                   code.method, code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
             },
             executorService);
@@ -852,7 +860,7 @@
           // StringBuilder/StringBuffer method invocations, and removeDeadCode() to remove
           // unused out-values.
           codeRewriter.rewriteMoveResult(code);
-          deadCodeRemover.run(code);
+          deadCodeRemover.run(code, Timing.empty());
           CodeRewriter.removeAssumeInstructions(appView, code);
           consumer.accept(code);
         },
@@ -871,7 +879,8 @@
     IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.method.holder));
     assert code != null;
     codeRewriter.rewriteMoveResult(code);
-    finalizeIR(method, code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
+    removeDeadCodeAndFinalizeIR(
+        method, code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
   }
 
   private void collectLambdaMergingCandidates(DexApplication application) {
@@ -896,7 +905,7 @@
       DexApplication.Builder<?> builder, ExecutorService executorService)
       throws ExecutionException {
     if (desugaredLibraryAPIConverter != null) {
-      desugaredLibraryAPIConverter.generateWrappers(builder, this, executorService);
+      desugaredLibraryAPIConverter.finalizeWrappers(builder, this, executorService);
     }
   }
 
@@ -922,8 +931,9 @@
       Log.debug(getClass(), "Initial (SSA) flow graph for %s:\n%s", method.toSourceString(), code);
     }
     assert code.isConsistentSSA();
-    code.traceBlocks();
     Timing timing = Timing.empty();
+    deadCodeRemover.run(code, timing);
+    code.traceBlocks();
     RegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing);
     method.setCode(code, registerAllocator, appView);
     if (Log.ENABLED) {
@@ -1328,9 +1338,7 @@
     // Dead code removal. Performed after simplifications to remove code that becomes dead
     // as a result of those simplifications. The following optimizations could reveal more
     // dead code which is removed right before register allocation in performRegisterAllocation.
-    timing.begin("Remove dead code");
-    deadCodeRemover.run(code);
-    timing.end();
+    deadCodeRemover.run(code, timing);
     assert code.isConsistentSSA();
 
     if (options.desugarState == DesugarState.ON && enableTryWithResourcesDesugaring()) {
@@ -1487,31 +1495,12 @@
 
     assert code.verifyTypes(appView);
 
+    deadCodeRemover.run(code, timing);
+
     if (appView.enableWholeProgramOptimizations()) {
-      if (libraryMethodOverrideAnalysis != null) {
-        timing.begin("Analyze library method overrides");
-        libraryMethodOverrideAnalysis.analyze(code);
-        timing.end();
-      }
-
-      if (fieldAccessAnalysis != null) {
-        timing.begin("Analyze field accesses");
-        fieldAccessAnalysis.recordFieldAccesses(code, feedback, methodProcessor);
-        if (classInitializerDefaultsResult != null) {
-          fieldAccessAnalysis.acceptClassInitializerDefaultsResult(classInitializerDefaultsResult);
-        }
-        timing.end();
-      }
-
-      // Arguments can be changed during the debug mode.
-      if (!isDebugMode && appView.callSiteOptimizationInfoPropagator() != null) {
-        timing.begin("Collect call-site info");
-        appView.callSiteOptimizationInfoPropagator().collectCallSiteOptimizationInfo(code);
-        timing.end();
-      }
-
       timing.begin("Collect optimization info");
-      collectOptimizationInfo(code, classInitializerDefaultsResult, feedback);
+      collectOptimizationInfo(
+          method, code, classInitializerDefaultsResult, feedback, methodProcessor, timing);
       timing.end();
     }
 
@@ -1546,17 +1535,50 @@
   // Compute optimization info summary for the current method unless it is pinned
   // (in that case we should not be making any assumptions about the behavior of the method).
   public void collectOptimizationInfo(
+      DexEncodedMethod method,
       IRCode code,
       ClassInitializerDefaultsResult classInitializerDefaultsResult,
-      OptimizationFeedback feedback) {
+      OptimizationFeedback feedback,
+      MethodProcessor methodProcessor,
+      Timing timing) {
+    if (libraryMethodOverrideAnalysis != null) {
+      timing.begin("Analyze library method overrides");
+      libraryMethodOverrideAnalysis.analyze(code);
+      timing.end();
+    }
+
+    if (fieldAccessAnalysis != null) {
+      timing.begin("Analyze field accesses");
+      fieldAccessAnalysis.recordFieldAccesses(code, feedback, methodProcessor);
+      if (classInitializerDefaultsResult != null) {
+        fieldAccessAnalysis.acceptClassInitializerDefaultsResult(classInitializerDefaultsResult);
+      }
+      timing.end();
+    }
+
+    // Arguments can be changed during the debug mode.
+    boolean isDebugMode = options.debug || method.getOptimizationInfo().isReachabilitySensitive();
+    if (!isDebugMode && appView.callSiteOptimizationInfoPropagator() != null) {
+      timing.begin("Collect call-site info");
+      appView.callSiteOptimizationInfoPropagator().collectCallSiteOptimizationInfo(code);
+      timing.end();
+    }
+
     if (appView.appInfo().withLiveness().isPinned(code.method.method)) {
       return;
     }
+
     methodOptimizationInfoCollector
         .collectMethodOptimizationInfo(code.method, code, feedback, dynamicTypeOptimization);
     FieldValueAnalysis.run(appView, code, classInitializerDefaultsResult, feedback, code.method);
   }
 
+  public void removeDeadCodeAndFinalizeIR(
+      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
+    deadCodeRemover.run(code, timing);
+    finalizeIR(method, code, feedback, timing);
+  }
+
   public void finalizeIR(
       DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
     code.traceBlocks();
@@ -1581,7 +1603,7 @@
   private void finalizeToCf(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     assert !method.getCode().isDexCode();
     CfBuilder builder = new CfBuilder(appView, method, code);
-    CfCode result = builder.build(codeRewriter);
+    CfCode result = builder.build(deadCodeRemover);
     method.setCode(result, appView);
     markProcessed(method, code, feedback);
   }
@@ -1633,9 +1655,7 @@
       IRCode code, DexEncodedMethod method, Timing timing) {
     // Always perform dead code elimination before register allocation. The register allocator
     // does not allow dead code (to make sure that we do not waste registers for unneeded values).
-    timing.begin("Remove dead code");
-    deadCodeRemover.run(code);
-    timing.end();
+    assert deadCodeRemover.verifyNoDeadCode(code);
     materializeInstructionBeforeLongOperationsWorkaround(code);
     workaroundForwardingInitializerBug(code);
     timing.begin("Allocate registers");
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index a15cbe4..ca144f7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -60,7 +60,7 @@
   void setInstanceInitializerInfo(
       DexEncodedMethod method, InstanceInitializerInfo instanceInitializerInfo);
 
-  void setInitializerEnablingJavaAssertions(DexEncodedMethod method);
+  void setInitializerEnablingJavaVmAssertions(DexEncodedMethod method);
 
   void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo);
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
index 43b801e..b55f54f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
@@ -26,7 +26,7 @@
     return new OneTimeMethodProcessor(null);
   }
 
-  static OneTimeMethodProcessor getInstance(Collection<DexEncodedMethod> methodsToProcess) {
+  public static OneTimeMethodProcessor getInstance(Collection<DexEncodedMethod> methodsToProcess) {
     return new OneTimeMethodProcessor(methodsToProcess);
   }
 
@@ -40,9 +40,8 @@
     return wave != null && wave.contains(method);
   }
 
-  <E extends Exception> void forEachWave(
-      ThrowingConsumer<DexEncodedMethod, E> consumer,
-      ExecutorService executorService)
+  public <E extends Exception> void forEachWave(
+      ThrowingConsumer<DexEncodedMethod, E> consumer, ExecutorService executorService)
       throws ExecutionException {
     ThreadUtils.processItems(wave, consumer, executorService);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PartialCallGraphBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/PartialCallGraphBuilder.java
index ef446d1..bfaf62c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PartialCallGraphBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PartialCallGraphBuilder.java
@@ -21,7 +21,7 @@
   }
 
   @Override
-  void process(ExecutorService executorService) throws ExecutionException {
+  void populateGraph(ExecutorService executorService) throws ExecutionException {
     ThreadUtils.processItems(seeds, this::processMethod, executorService);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index acba17d..8fb6afb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
-import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.IROrdering;
@@ -20,7 +19,6 @@
 import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.Deque;
-import java.util.Iterator;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -81,18 +79,12 @@
     Set<DexEncodedMethod> reprocessing = Sets.newIdentityHashSet();
     int waveCount = 1;
     while (!nodes.isEmpty()) {
-      Set<DexEncodedMethod> wave = Sets.newIdentityHashSet();
-      extractLeaves(
-          nodes,
-          leaf -> {
-            wave.add(leaf.method);
-
-            // Reprocess methods that invoke a method with a single call site.
-            if (callSiteInformation.hasSingleCallSite(leaf.method.method)) {
-              callGraph.cycleEliminationResult.forEachRemovedCaller(
-                  leaf, caller -> reprocessing.add(caller.method));
-            }
-          });
+      Set<DexEncodedMethod> wave = callGraph.extractLeaves();
+      for (DexEncodedMethod method : wave) {
+        if (callSiteInformation.hasSingleCallSite(method.method)) {
+          callGraph.cycleEliminationResult.forEachRemovedCaller(method, reprocessing::add);
+        }
+      }
       waves.addLast(shuffle.order(wave));
       if (Log.ENABLED && Log.isLoggingEnabledFor(PrimaryMethodProcessor.class)) {
         Log.info(getClass(), "Wave #%d: %d", waveCount++, wave.size());
@@ -105,26 +97,6 @@
     return waves;
   }
 
-  /**
-   * Extract the next set of leaves (nodes with an outgoing call degree of 0) if any.
-   *
-   * <p>All nodes in the graph are extracted if called repeatedly until null is returned. Please
-   * note that there are no cycles in this graph (see {@link CycleEliminator#breakCycles}).
-   */
-  static void extractLeaves(Set<Node> nodes, Consumer<Node> fn) {
-    Set<Node> removed = Sets.newIdentityHashSet();
-    Iterator<Node> nodeIterator = nodes.iterator();
-    while (nodeIterator.hasNext()) {
-      Node node = nodeIterator.next();
-      if (node.isLeaf()) {
-        fn.accept(node);
-        nodeIterator.remove();
-        removed.add(node);
-      }
-    }
-    removed.forEach(Node::cleanCallersForRemoval);
-  }
-
   @Override
   public boolean isProcessedConcurrently(DexEncodedMethod method) {
     return wave != null && wave.contains(method);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index fe87616..6ff25d9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -4,12 +4,18 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
+
+import com.android.tools.r8.dex.ApplicationReader;
 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.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -26,11 +32,13 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.backports.BackportedMethods;
 import com.android.tools.r8.ir.desugar.backports.BooleanMethodRewrites;
@@ -43,11 +51,14 @@
 import com.android.tools.r8.ir.desugar.backports.OptionalMethodRewrites;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 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.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -92,15 +103,29 @@
             && appView.options().minApiLevel <= AndroidApiLevel.LATEST.getLevel();
   }
 
-  public static List<DexMethod> generateListOfBackportedMethods(AndroidApiLevel apiLevel) {
-    List<DexMethod> methods = new ArrayList<>();
-    InternalOptions options = new InternalOptions();
-    options.minApiLevel = apiLevel.getLevel();
-    AppView<?> appView = AppView.createForD8(null, options);
-    BackportedMethodRewriter.RewritableMethods rewritableMethods =
-        new BackportedMethodRewriter.RewritableMethods(options, appView);
-    rewritableMethods.visit(methods::add);
-    return methods;
+  public static List<DexMethod> generateListOfBackportedMethods(
+      AndroidApp androidApp, InternalOptions options, ExecutorService executor) throws IOException {
+    try {
+      List<DexMethod> methods = new ArrayList<>();
+      PrefixRewritingMapper rewritePrefix =
+          options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
+      AppInfo appInfo = null;
+      if (androidApp != null) {
+        DexApplication app =
+            new ApplicationReader(androidApp, options, Timing.empty()).read(executor);
+        appInfo =
+            options.desugaredLibraryConfiguration.getRewritePrefix().isEmpty()
+                ? new AppInfo(app)
+                : new AppInfoWithClassHierarchy(app);
+      }
+      AppView<?> appView = AppView.createForD8(appInfo, options, rewritePrefix);
+      BackportedMethodRewriter.RewritableMethods rewritableMethods =
+          new BackportedMethodRewriter.RewritableMethods(options, appView);
+      rewritableMethods.visit(methods::add);
+      return methods;
+    } catch (ExecutionException e) {
+      throw unwrapExecutionException(e);
+    }
   }
 
   public void desugar(IRCode code) {
@@ -108,6 +133,7 @@
       return; // Nothing to do!
     }
 
+    Set<Value> affectedValues = Sets.newIdentityHashSet();
     InstructionListIterator iterator = code.instructionListIterator();
     while (iterator.hasNext()) {
       Instruction instruction = iterator.next();
@@ -165,7 +191,7 @@
         }
       }
 
-      provider.rewriteInvoke(invoke, iterator, code, appView);
+      provider.rewriteInvoke(invoke, iterator, code, appView, affectedValues);
 
       if (provider.requiresGenerationOfCode()) {
         DexMethod newMethod = provider.provideMethod(appView);
@@ -173,6 +199,9 @@
         holders.add(code.method.method.holder);
       }
     }
+    if (!affectedValues.isEmpty()) {
+      new TypeAnalysis(appView).narrowing(affectedValues);
+    }
   }
 
   private Collection<DexProgramClass> findSynthesizedFrom(Builder<?> builder, DexType holder) {
@@ -1441,6 +1470,17 @@
           new MethodGenerator(
               method, BackportedMethods::CharacterMethods_toStringCodepoint, "toStringCodepoint"));
 
+      // CharSequence
+      type = factory.charSequenceType;
+
+      // int CharSequence.compare(CharSequence, CharSequence)
+      name = factory.createString("compare");
+      proto =
+          factory.createProto(factory.intType, factory.charSequenceType, factory.charSequenceType);
+      method = factory.createMethod(type, proto, name);
+      addProvider(
+          new MethodGenerator(method, BackportedMethods::CharSequenceMethods_compare, "compare"));
+
       // String
       type = factory.stringType;
 
@@ -1574,10 +1614,10 @@
           };
       MethodInvokeRewriter[] rewriters =
           new MethodInvokeRewriter[] {
-              OptionalMethodRewrites::rewriteOrElseGet,
-              OptionalMethodRewrites::rewriteDoubleOrElseGet,
-              OptionalMethodRewrites::rewriteLongOrElseGet,
-              OptionalMethodRewrites::rewriteIntOrElseGet,
+            OptionalMethodRewrites::rewriteOrElseGet,
+            OptionalMethodRewrites::rewriteDoubleOrElseGet,
+            OptionalMethodRewrites::rewriteLongOrElseGet,
+            OptionalMethodRewrites::rewriteIntOrElseGet,
           };
       DexString name = factory.createString("orElseThrow");
       for (int i = 0; i < optionalTypes.length; i++) {
@@ -1714,8 +1754,12 @@
       this.method = method;
     }
 
-    public abstract void rewriteInvoke(InvokeMethod invoke, InstructionListIterator iterator,
-        IRCode code, AppView<?> appView);
+    public abstract void rewriteInvoke(
+        InvokeMethod invoke,
+        InstructionListIterator iterator,
+        IRCode code,
+        AppView<?> appView,
+        Set<Value> affectedValues);
 
     public abstract DexMethod provideMethod(AppView<?> appView);
 
@@ -1737,8 +1781,12 @@
     }
 
     @Override
-    public void rewriteInvoke(InvokeMethod invoke, InstructionListIterator iterator, IRCode code,
-        AppView<?> appView) {
+    public void rewriteInvoke(
+        InvokeMethod invoke,
+        InstructionListIterator iterator,
+        IRCode code,
+        AppView<?> appView,
+        Set<Value> affectedValues) {
       iterator.replaceCurrentInstruction(
           new InvokeStatic(provideMethod(appView), invoke.outValue(), invoke.inValues()));
     }
@@ -1777,8 +1825,12 @@
 
     @Override
     public void rewriteInvoke(
-        InvokeMethod invoke, InstructionListIterator iterator, IRCode code, AppView<?> appView) {
-      rewriter.rewrite(invoke, iterator, appView.dexItemFactory());
+        InvokeMethod invoke,
+        InstructionListIterator iterator,
+        IRCode code,
+        AppView<?> appView,
+        Set<Value> affectedValues) {
+      rewriter.rewrite(invoke, iterator, appView.dexItemFactory(), affectedValues);
       assert code.isConsistentSSA();
     }
 
@@ -1815,8 +1867,12 @@
     }
 
     @Override
-    public void rewriteInvoke(InvokeMethod invoke, InstructionListIterator iterator, IRCode code,
-        AppView<?> appView) {
+    public void rewriteInvoke(
+        InvokeMethod invoke,
+        InstructionListIterator iterator,
+        IRCode code,
+        AppView<?> appView,
+        Set<Value> affectedValues) {
       iterator.replaceCurrentInstruction(
           new InvokeStatic(provideMethod(appView), invoke.outValue(), invoke.inValues()));
     }
@@ -1895,6 +1951,10 @@
 
   private interface MethodInvokeRewriter {
 
-    void rewrite(InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory);
+    void rewrite(
+        InvokeMethod invoke,
+        InstructionListIterator iterator,
+        DexItemFactory factory,
+        Set<Value> affectedValues);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index dc0e8d6..ceed9ef 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -8,9 +8,11 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
@@ -60,17 +62,27 @@
 public class DesugaredLibraryAPIConverter {
 
   static final String VIVIFIED_PREFIX = "$-vivified-$.";
+  private static final String DESCRIPTOR_VIVIFIED_PREFIX = "L$-vivified-$/";
 
   private final AppView<?> appView;
   private final DexItemFactory factory;
+  // For debugging only, allows to assert that synthesized code in R8 have been synthesized in the
+  // Enqueuer and not during IR processing.
+  private final Mode mode;
   private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizor;
   private final Map<DexClass, Set<DexEncodedMethod>> callBackMethods = new HashMap<>();
   private final Set<DexMethod> trackedCallBackAPIs;
   private final Set<DexMethod> trackedAPIs;
 
-  public DesugaredLibraryAPIConverter(AppView<?> appView) {
+  public enum Mode {
+    GENERATE_CALLBACKS_AND_WRAPPERS,
+    ASSERT_CALLBACKS_AND_WRAPPERS_GENERATED;
+  }
+
+  public DesugaredLibraryAPIConverter(AppView<?> appView, Mode mode) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
+    this.mode = mode;
     this.wrapperSynthesizor = new DesugaredLibraryWrapperSynthesizer(appView, this);
     if (appView.options().testing.trackDesugaredAPIConversions) {
       trackedCallBackAPIs = Sets.newConcurrentHashSet();
@@ -81,13 +93,25 @@
     }
   }
 
+  public static boolean isVivifiedType(DexType type) {
+    return type.descriptor.toString().startsWith(DESCRIPTOR_VIVIFIED_PREFIX);
+  }
+
+  boolean canGenerateWrappersAndCallbacks() {
+    return mode == Mode.GENERATE_CALLBACKS_AND_WRAPPERS;
+  }
+
   public void desugar(IRCode code) {
 
     if (wrapperSynthesizor.hasSynthesized(code.method.method.holder)) {
       return;
     }
 
-    generateCallBackIfNeeded(code);
+    if (!canGenerateWrappersAndCallbacks()) {
+      assert validateCallbackWasGeneratedInEnqueuer(code.method);
+    } else {
+      registerCallbackIfRequired(code.method);
+    }
 
     ListIterator<BasicBlock> blockIterator = code.listIterator();
     while (blockIterator.hasNext()) {
@@ -100,25 +124,47 @@
         }
         InvokeMethod invokeMethod = instruction.asInvokeMethod();
         DexMethod invokedMethod = invokeMethod.getInvokedMethod();
-        // Rewriting is required only on calls to library methods which are not desugared.
-        if (appView.rewritePrefix.hasRewrittenType(invokedMethod.holder, appView)
-            || invokedMethod.holder.isArrayType()) {
-          continue;
-        }
-        DexClass dexClass = appView.definitionFor(invokedMethod.holder);
-        if (dexClass == null || !dexClass.isLibraryClass()) {
-          continue;
-        }
         // Library methods do not understand desugared types, hence desugared types have to be
         // converted around non desugared library calls for the invoke to resolve.
-        if (appView.rewritePrefix.hasRewrittenTypeInSignature(invokedMethod.proto, appView)) {
+        if (shouldRewriteInvoke(invokedMethod)) {
           rewriteLibraryInvoke(code, invokeMethod, iterator, blockIterator);
         }
       }
     }
   }
 
-  private void generateCallBackIfNeeded(IRCode code) {
+  private boolean validateCallbackWasGeneratedInEnqueuer(DexEncodedMethod encodedMethod) {
+    if (!shouldRegisterCallback(encodedMethod)) {
+      return true;
+    }
+    DexProgramClass holderClass = appView.definitionForProgramType(encodedMethod.method.holder);
+    DexMethod installedCallback =
+        methodWithVivifiedTypeInSignature(encodedMethod.method, holderClass.type, appView);
+    assert holderClass.lookupMethod(installedCallback) != null;
+    return true;
+  }
+
+  private boolean shouldRewriteInvoke(DexMethod invokedMethod) {
+    if (appView.rewritePrefix.hasRewrittenType(invokedMethod.holder, appView)
+        || invokedMethod.holder.isArrayType()) {
+      return false;
+    }
+    DexClass dexClass = appView.definitionFor(invokedMethod.holder);
+    if (dexClass == null || !dexClass.isLibraryClass()) {
+      return false;
+    }
+    return appView.rewritePrefix.hasRewrittenTypeInSignature(invokedMethod.proto, appView);
+  }
+
+  public void registerCallbackIfRequired(DexEncodedMethod encodedMethod) {
+    if (shouldRegisterCallback(encodedMethod)) {
+      DexClass dexClass = appView.definitionFor(encodedMethod.method.holder);
+      assert dexClass != null;
+      registerCallback(dexClass, encodedMethod);
+    }
+  }
+
+  private boolean shouldRegisterCallback(DexEncodedMethod encodedMethod) {
     // Any override of a library method can be called by the library.
     // We duplicate the method to have a vivified type version callable by the library and
     // a type version callable by the program. We need to add the vivified version to the rootset
@@ -126,10 +172,10 @@
     // library type), but the enqueuer cannot see that.
     // To avoid too much computation we first look if the method would need to be rewritten if
     // it would override a library method, then check if it overrides a library method.
-    if (code.method.isPrivateMethod() || code.method.isStatic()) {
-      return;
+    if (encodedMethod.isPrivateMethod() || encodedMethod.isStatic()) {
+      return false;
     }
-    DexMethod method = code.method.method;
+    DexMethod method = encodedMethod.method;
     if (method.holder.isArrayType()
         || !appView.rewritePrefix.hasRewrittenTypeInSignature(method.proto, appView)
         || appView
@@ -137,17 +183,16 @@
             .desugaredLibraryConfiguration
             .getEmulateLibraryInterface()
             .containsKey(method.holder)) {
-      return;
+      return false;
     }
     DexClass dexClass = appView.definitionFor(method.holder);
     if (dexClass == null) {
-      return;
+      return false;
     }
-    if (overridesLibraryMethod(dexClass, method)) {
-      generateCallBack(dexClass, code.method);
-    }
+    return overridesLibraryMethod(dexClass, method);
   }
 
+
   private boolean overridesLibraryMethod(DexClass theClass, DexMethod method) {
     // We look up everywhere to see if there is a supertype/interface implementing the method...
     LinkedList<DexType> workList = new LinkedList<>();
@@ -182,17 +227,19 @@
     return foundOverrideToRewrite;
   }
 
-  private synchronized void generateCallBack(DexClass dexClass, DexEncodedMethod originalMethod) {
-    if (dexClass.isInterface()
-        && originalMethod.isDefaultMethod()
-        && (!appView.options().canUseDefaultAndStaticInterfaceMethods()
-            || appView.options().isDesugaredLibraryCompilation())) {
-      // Interface method desugaring has been performed before and all the call-backs will be
-      // generated in all implementors of the interface. R8 cannot introduce new
-      // default methods at this point, but R8 does not need to do anything (the interface
-      // already implements the vivified version through inheritance, and all implementors
-      // support the call-back correctly).
-      return;
+  private synchronized void registerCallback(DexClass dexClass, DexEncodedMethod originalMethod) {
+    // In R8 we should be in the enqueuer, therefore we can duplicate a default method and both
+    // methods will be desugared.
+    // In D8, this happens after interface method desugaring, we cannot introduce new default
+    // methods, but we do not need to since this is a library override (invokes will resolve) and
+    // all implementors have been enhanced with a forwarding method which will be duplicated.
+    if (!appView.enableWholeProgramOptimizations()) {
+      if (dexClass.isInterface()
+          && originalMethod.isDefaultMethod()
+          && (!appView.options().canUseDefaultAndStaticInterfaceMethods()
+              || appView.options().isDesugaredLibraryCompilation())) {
+        return;
+      }
     }
     if (trackedCallBackAPIs != null) {
       trackedCallBackAPIs.add(originalMethod.method);
@@ -225,25 +272,46 @@
     return appView.dexItemFactory().createMethod(holder, newProto, originalMethod.name);
   }
 
-  public void generateWrappers(
+  public void finalizeWrappers(
       DexApplication.Builder<?> builder, IRConverter irConverter, ExecutorService executorService)
       throws ExecutionException {
+    // In D8, we generate the wrappers here. In R8, wrappers have already been generated in the
+    // enqueuer, so nothing needs to be done.
+    if (appView.enableWholeProgramOptimizations()) {
+      return;
+    }
+    List<DexEncodedMethod> callbacks = generateCallbackMethods();
+    irConverter.processMethodsConcurrently(callbacks, executorService);
+    wrapperSynthesizor.finalizeWrappersForD8(builder, irConverter, executorService);
+  }
+
+  public List<DexEncodedMethod> generateCallbackMethods() {
     if (appView.options().testing.trackDesugaredAPIConversions) {
       generateTrackDesugaredAPIWarnings(trackedAPIs, "");
       generateTrackDesugaredAPIWarnings(trackedCallBackAPIs, "callback ");
     }
+    List<DexEncodedMethod> result = new ArrayList<>();
     for (DexClass dexClass : callBackMethods.keySet()) {
-      Set<DexEncodedMethod> dexEncodedMethods =
+      List<DexEncodedMethod> dexEncodedMethods =
           generateCallbackMethods(callBackMethods.get(dexClass), dexClass);
       dexClass.appendVirtualMethods(dexEncodedMethods);
-      irConverter.processMethodsConcurrently(dexEncodedMethods, executorService);
+      result.addAll(dexEncodedMethods);
     }
-    wrapperSynthesizor.finalizeWrappers(builder, irConverter, executorService);
+    return result;
   }
 
-  private Set<DexEncodedMethod> generateCallbackMethods(
+  public Map<DexProgramClass, DexProgramClass> synthesizeWrappersAndMapToReverse() {
+    return wrapperSynthesizor.synthesizeWrappersAndMapToReverse();
+  }
+
+  public DexClasspathClass synthesizeClasspathMock(
+      DexClass classToMock, DexType mockType, boolean mockIsInterface) {
+    return wrapperSynthesizor.synthesizeClasspathMock(classToMock, mockType, mockIsInterface);
+  }
+
+  private List<DexEncodedMethod> generateCallbackMethods(
       Set<DexEncodedMethod> originalMethods, DexClass dexClass) {
-    Set<DexEncodedMethod> newDexEncodedMethods = new HashSet<>();
+    List<DexEncodedMethod> newDexEncodedMethods = new ArrayList<>();
     for (DexEncodedMethod originalMethod : originalMethods) {
       DexMethod methodToInstall =
           methodWithVivifiedTypeInSignature(originalMethod.method, dexClass.type, appView);
@@ -256,6 +324,7 @@
       newDexEncodedMethod.setCode(cfCode, appView);
       newDexEncodedMethods.add(newDexEncodedMethod);
     }
+    assert Sets.newHashSet(newDexEncodedMethods).size() == newDexEncodedMethods.size();
     return newDexEncodedMethods;
   }
 
@@ -296,6 +365,24 @@
     return vivifiedType;
   }
 
+  public void registerWrappersForLibraryInvokeIfRequired(DexMethod invokedMethod) {
+    if (!shouldRewriteInvoke(invokedMethod)) {
+      return;
+    }
+    if (trackedAPIs != null) {
+      trackedAPIs.add(invokedMethod);
+    }
+    DexType returnType = invokedMethod.proto.returnType;
+    if (appView.rewritePrefix.hasRewrittenType(returnType, appView) && canConvert(returnType)) {
+      registerConversionWrappers(returnType, vivifiedTypeFor(returnType, appView));
+    }
+    for (DexType argType : invokedMethod.proto.parameters.values) {
+      if (appView.rewritePrefix.hasRewrittenType(argType, appView) && canConvert(argType)) {
+        registerConversionWrappers(argType, argType);
+      }
+    }
+  }
+
   private void rewriteLibraryInvoke(
       IRCode code,
       InvokeMethod invokeMethod,
@@ -434,9 +521,23 @@
       IRCode code, InvokeMethod invokeMethod, DexType returnType, DexType returnVivifiedType) {
     DexMethod conversionMethod = createConversionMethod(returnType, returnVivifiedType, returnType);
     Value convertedValue = createConversionValue(code, Nullability.maybeNull(), returnType);
-    invokeMethod.outValue().replaceUsers(convertedValue);
-    return new InvokeStatic(
-        conversionMethod, convertedValue, Collections.singletonList(invokeMethod.outValue()));
+    Value outValue = invokeMethod.outValue();
+    outValue.replaceUsers(convertedValue);
+    // The only user of out value is now the new invoke static, so no type propagation is required.
+    outValue.setTypeLattice(
+        TypeLatticeElement.fromDexType(
+            returnVivifiedType, outValue.getTypeLattice().nullability(), appView));
+    return new InvokeStatic(conversionMethod, convertedValue, Collections.singletonList(outValue));
+  }
+
+  private void registerConversionWrappers(DexType type, DexType srcType) {
+    if (appView.options().desugaredLibraryConfiguration.getCustomConversions().get(type) == null) {
+      if (type == srcType) {
+        wrapperSynthesizor.getTypeWrapper(type);
+      } else {
+        wrapperSynthesizor.getVivifiedTypeWrapper(type);
+      }
+    }
   }
 
   public DexMethod createConversionMethod(DexType type, DexType srcType, DexType destType) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index bf48a16..069015f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -12,6 +13,7 @@
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -19,6 +21,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
+import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.FieldAccessFlags;
@@ -31,13 +34,14 @@
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperConversionCfCodeProvider;
 import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.utils.Box;
-import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.IdentityHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -45,8 +49,6 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.function.BiConsumer;
-import java.util.function.BiFunction;
 
 // I am responsible for the generation of wrappers used to call library APIs when desugaring
 // libraries. Wrappers can be both ways, wrapping the desugarType as a type, or the type as
@@ -98,37 +100,36 @@
   public static final String VIVIFIED_TYPE_WRAPPER_SUFFIX = "$-V-WRP";
 
   private final AppView<?> appView;
-  private final Map<DexType, Pair<DexType, DexProgramClass>> typeWrappers =
-      new ConcurrentHashMap<>();
-  private final Map<DexType, Pair<DexType, DexProgramClass>> vivifiedTypeWrappers =
-      new ConcurrentHashMap<>();
+  private final DexString dexWrapperPrefix;
+  private final Map<DexType, DexType> typeWrappers = new ConcurrentHashMap<>();
+  private final Map<DexType, DexType> vivifiedTypeWrappers = new ConcurrentHashMap<>();
   // The invalidWrappers are wrappers with incorrect behavior because of final methods that could
   // not be overridden. Such wrappers are awful because the runtime behavior is undefined and does
   // not raise explicit errors. So we register them here and conversion methods for such wrappers
   // raise a runtime exception instead of generating the wrapper.
   private final Set<DexType> invalidWrappers = Sets.newConcurrentHashSet();
-  private final Set<DexType> generatedWrappers = Sets.newConcurrentHashSet();
   private final DexItemFactory factory;
   private final DesugaredLibraryAPIConverter converter;
+  private final DexString vivifiedSourceFile;
 
   DesugaredLibraryWrapperSynthesizer(AppView<?> appView, DesugaredLibraryAPIConverter converter) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
+    this.dexWrapperPrefix = factory.createString("L" + WRAPPER_PREFIX);
     this.converter = converter;
+    this.vivifiedSourceFile = appView.dexItemFactory().createString("vivified");
   }
 
-  public static boolean isSynthesizedWrapper(DexType clazz) {
-    return clazz.descriptor.toString().contains(WRAPPER_PREFIX);
+  public static boolean isSynthesizedWrapper(DexType type) {
+    // Slow path, but more convenient since no instance is needed. Use hasSynthesized(DexType) when
+    // possible.
+    return type.descriptor.toString().startsWith("L" + WRAPPER_PREFIX);
   }
 
   boolean hasSynthesized(DexType type) {
-    return generatedWrappers.contains(type);
+    return type.descriptor.startsWith(dexWrapperPrefix);
   }
 
-  // Wrapper initial generation section.
-  // 1. Generate wrappers without conversion methods.
-  // 2. Compute wrapper types.
-
   boolean canGenerateWrapper(DexType type) {
     DexClass dexClass = appView.definitionFor(type);
     if (dexClass == null) {
@@ -138,15 +139,11 @@
   }
 
   DexType getTypeWrapper(DexType type) {
-    return getWrapper(type, TYPE_WRAPPER_SUFFIX, typeWrappers, this::generateTypeWrapper);
+    return getWrapper(type, TYPE_WRAPPER_SUFFIX, typeWrappers);
   }
 
   DexType getVivifiedTypeWrapper(DexType type) {
-    return getWrapper(
-        type,
-        VIVIFIED_TYPE_WRAPPER_SUFFIX,
-        vivifiedTypeWrappers,
-        this::generateVivifiedTypeWrapper);
+    return getWrapper(type, VIVIFIED_TYPE_WRAPPER_SUFFIX, vivifiedTypeWrappers);
   }
 
   private DexType createWrapperType(DexType type, String suffix) {
@@ -164,46 +161,35 @@
             + ";");
   }
 
-  private DexType getWrapper(
-      DexType type,
-      String suffix,
-      Map<DexType, Pair<DexType, DexProgramClass>> wrappers,
-      BiFunction<DexClass, DexType, DexProgramClass> wrapperGenerator) {
-    // Answers the DexType of the wrapper. Generate the wrapper DexProgramClass if not already done,
-    // except the conversions methods. Conversion method generation is postponed to know if the
-    // reverse wrapper is present at generation time.
-    // We generate the type while locking the concurrent hash map, but we release the lock before
-    // generating the actual class to avoid locking for too long (hence the Pair).
+  private DexType getWrapper(DexType type, String suffix, Map<DexType, DexType> wrappers) {
     assert !type.toString().startsWith(DesugaredLibraryAPIConverter.VIVIFIED_PREFIX);
-    Box<Boolean> toGenerate = new Box<>(false);
-    Pair<DexType, DexProgramClass> pair =
-        wrappers.computeIfAbsent(
-            type,
-            t -> {
-              toGenerate.set(true);
-              DexType wrapperType = createWrapperType(type, suffix);
-              generatedWrappers.add(wrapperType);
-              return new Pair<>(wrapperType, null);
-            });
-    if (toGenerate.get()) {
-      assert pair.getSecond() == null;
-      DexClass dexClass = appView.definitionFor(type);
-      // The dexClass should be a library class, so it cannot be null.
-      assert dexClass != null
-          && (dexClass.isLibraryClass() || appView.options().isDesugaredLibraryCompilation());
-      if (dexClass.accessFlags.isFinal()) {
-        throw appView
-            .options()
-            .reporter
-            .fatalError(
-                new StringDiagnostic(
-                    "Cannot generate a wrapper for final class "
-                        + dexClass.type
-                        + ". Add a custom conversion in the desugared library."));
-      }
-      pair.setSecond(wrapperGenerator.apply(dexClass, pair.getFirst()));
+    return wrappers.computeIfAbsent(
+        type,
+        t -> {
+          DexType wrapperType = createWrapperType(type, suffix);
+          assert converter.canGenerateWrappersAndCallbacks()
+                  || appView.definitionForProgramType(wrapperType) != null
+              : "Wrapper " + wrapperType + " should have been generated in the enqueuer.";
+          return wrapperType;
+        });
+  }
+
+  private DexClass getValidClassToWrap(DexType type) {
+    DexClass dexClass = appView.definitionFor(type);
+    // The dexClass should be a library class, so it cannot be null.
+    assert dexClass != null
+        && (dexClass.isLibraryClass() || appView.options().isDesugaredLibraryCompilation());
+    if (dexClass.accessFlags.isFinal()) {
+      throw appView
+          .options()
+          .reporter
+          .fatalError(
+              new StringDiagnostic(
+                  "Cannot generate a wrapper for final class "
+                      + dexClass.type
+                      + ". Add a custom conversion in the desugared library."));
     }
-    return pair.getFirst();
+    return dexClass;
   }
 
   private DexType vivifiedTypeFor(DexType type) {
@@ -212,11 +198,12 @@
 
   private DexProgramClass generateTypeWrapper(DexClass dexClass, DexType typeWrapperType) {
     DexType type = dexClass.type;
-    DexEncodedField wrapperField = synthesizeWrappedValueField(typeWrapperType, type);
+    DexEncodedField wrapperField = synthesizeWrappedValueEncodedField(typeWrapperType, type);
     return synthesizeWrapper(
         vivifiedTypeFor(type),
         dexClass,
         synthesizeVirtualMethodsForTypeWrapper(dexClass, wrapperField),
+        generateTypeConversion(type, typeWrapperType),
         wrapperField);
   }
 
@@ -224,11 +211,12 @@
       DexClass dexClass, DexType vivifiedTypeWrapperType) {
     DexType type = dexClass.type;
     DexEncodedField wrapperField =
-        synthesizeWrappedValueField(vivifiedTypeWrapperType, vivifiedTypeFor(type));
+        synthesizeWrappedValueEncodedField(vivifiedTypeWrapperType, vivifiedTypeFor(type));
     return synthesizeWrapper(
         type,
         dexClass,
         synthesizeVirtualMethodsForVivifiedTypeWrapper(dexClass, wrapperField),
+        generateVivifiedTypeConversion(type, vivifiedTypeWrapperType),
         wrapperField);
   }
 
@@ -236,6 +224,7 @@
       DexType wrappingType,
       DexClass clazz,
       DexEncodedMethod[] virtualMethods,
+      DexEncodedMethod conversionMethod,
       DexEncodedField wrapperField) {
     boolean isItf = clazz.isInterface();
     DexType superType = isItf ? factory.objectType : wrappingType;
@@ -257,9 +246,7 @@
         DexAnnotationSet.empty(),
         DexEncodedField.EMPTY_ARRAY, // No static fields.
         new DexEncodedField[] {wrapperField},
-        new DexEncodedMethod[] {
-          synthesizeConstructor(wrapperField.field)
-        }, // Conversions methods will be added later.
+        new DexEncodedMethod[] {synthesizeConstructor(wrapperField.field), conversionMethod},
         virtualMethods,
         factory.getSkipNameValidationForTesting(),
         getChecksumSupplier(this, clazz.type),
@@ -365,6 +352,51 @@
     return finalizeWrapperMethods(generatedMethods, finalMethods);
   }
 
+  private DexEncodedMethod[] synthesizeVirtualMethodsForClasspathMock(
+      DexClass dexClass, DexType mockType) {
+    List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
+    List<DexEncodedMethod> generatedMethods = new ArrayList<>();
+    // Generate only abstract methods for library override detection.
+    for (DexEncodedMethod dexEncodedMethod : dexMethods) {
+      DexClass holderClass = appView.definitionFor(dexEncodedMethod.method.holder);
+      assert holderClass != null || appView.options().isDesugaredLibraryCompilation();
+      if (!dexEncodedMethod.isFinal()) {
+        DexMethod methodToInstall =
+            DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
+                dexEncodedMethod.method, mockType, appView);
+        DexEncodedMethod newDexEncodedMethod =
+            newSynthesizedMethod(methodToInstall, dexEncodedMethod, null);
+        generatedMethods.add(newDexEncodedMethod);
+      }
+    }
+    return generatedMethods.toArray(DexEncodedMethod.EMPTY_ARRAY);
+  }
+
+  DexClasspathClass synthesizeClasspathMock(
+      DexClass classToMock, DexType mockType, boolean mockIsInterface) {
+    return new DexClasspathClass(
+        mockType,
+        Kind.CF,
+        new SynthesizedOrigin("Desugared library wrapper super class ", getClass()),
+        ClassAccessFlags.fromDexAccessFlags(
+            Constants.ACC_SYNTHETIC
+                | Constants.ACC_PUBLIC
+                | (BooleanUtils.intValue(mockIsInterface) * Constants.ACC_INTERFACE)),
+        appView.dexItemFactory().objectType,
+        DexTypeList.empty(),
+        vivifiedSourceFile,
+        null,
+        Collections.emptyList(),
+        null,
+        Collections.emptyList(),
+        DexAnnotationSet.empty(),
+        DexEncodedField.EMPTY_ARRAY,
+        DexEncodedField.EMPTY_ARRAY,
+        DexEncodedMethod.EMPTY_ARRAY,
+        synthesizeVirtualMethodsForClasspathMock(classToMock, mockType),
+        appView.dexItemFactory().getSkipNameValidationForTesting());
+  }
+
   private DexEncodedMethod[] finalizeWrapperMethods(
       List<DexEncodedMethod> generatedMethods, Set<DexMethod> finalMethods) {
     if (finalMethods.isEmpty()) {
@@ -394,7 +426,11 @@
       DexMethod methodToInstall, DexEncodedMethod template, Code code) {
     MethodAccessFlags newFlags = template.accessFlags.copy();
     assert newFlags.isPublic();
-    newFlags.unsetAbstract();
+    if (code == null) {
+      newFlags.setAbstract();
+    } else {
+      newFlags.unsetAbstract();
+    }
     // TODO(b/146114533): Fix inlining in synthetic methods and remove unsetBridge.
     newFlags.unsetBridge();
     newFlags.setSynthetic();
@@ -446,8 +482,12 @@
     return implementedMethods;
   }
 
-  private DexEncodedField synthesizeWrappedValueField(DexType holder, DexType fieldType) {
-    DexField field = factory.createField(holder, fieldType, factory.wrapperFieldName);
+  private DexField wrappedValueField(DexType holder, DexType fieldType) {
+    return factory.createField(holder, fieldType, factory.wrapperFieldName);
+  }
+
+  private DexEncodedField synthesizeWrappedValueEncodedField(DexType holder, DexType fieldType) {
+    DexField field = wrappedValueField(holder, fieldType);
     // Field is package private to be accessible from convert methods without a getter.
     FieldAccessFlags fieldAccessFlags =
         FieldAccessFlags.fromCfAccessFlags(Constants.ACC_FINAL | Constants.ACC_SYNTHETIC);
@@ -479,86 +519,89 @@
         true);
   }
 
-  // Wrapper finalization section.
-  // 1. Generate conversions methods (convert(type)).
-  // 2. Add the synthesized classes.
-  // 3. Process all methods.
-
-  void finalizeWrappers(
+  void finalizeWrappersForD8(
       DexApplication.Builder<?> builder, IRConverter irConverter, ExecutorService executorService)
       throws ExecutionException {
-    assert verifyAllClassesGenerated();
-    // We register first, then optimize to avoid warnings due to missing parameter types.
-    registerWrappers(builder, typeWrappers);
-    registerWrappers(builder, vivifiedTypeWrappers);
-    finalizeWrappers(irConverter, executorService, typeWrappers, this::generateTypeConversions);
-    finalizeWrappers(
-        irConverter,
-        executorService,
-        vivifiedTypeWrappers,
-        this::generateVivifiedTypeConversions);
+    Map<DexType, DexProgramClass> synthesizedWrappers = synthesizeWrappers();
+    registerAndProcessWrappers(builder, irConverter, executorService, synthesizedWrappers.values());
   }
 
-  private void registerWrappers(
-      DexApplication.Builder<?> builder, Map<DexType, Pair<DexType, DexProgramClass>> wrappers) {
-    for (DexType type : wrappers.keySet()) {
-      DexProgramClass pgrmClass = wrappers.get(type).getSecond();
-      assert pgrmClass != null;
-      registerSynthesizedClass(pgrmClass, builder);
+  private Map<DexType, DexProgramClass> synthesizeWrappers() {
+    Map<DexType, DexProgramClass> synthesizedWrappers = new IdentityHashMap<>();
+    // Generating a wrapper may require other wrappers to be generated, iterate until fix point.
+    while (synthesizedWrappers.size() != typeWrappers.size() + vivifiedTypeWrappers.size()) {
+      for (DexType type : typeWrappers.keySet()) {
+        DexType typeWrapperType = typeWrappers.get(type);
+        if (!synthesizedWrappers.containsKey(typeWrapperType)) {
+          synthesizedWrappers.put(
+              typeWrapperType, generateTypeWrapper(getValidClassToWrap(type), typeWrapperType));
+        }
+      }
+      for (DexType type : vivifiedTypeWrappers.keySet()) {
+        DexType vivifiedTypeWrapperType = vivifiedTypeWrappers.get(type);
+        if (!synthesizedWrappers.containsKey(vivifiedTypeWrapperType)) {
+          synthesizedWrappers.put(
+              vivifiedTypeWrapperType,
+              generateVivifiedTypeWrapper(getValidClassToWrap(type), vivifiedTypeWrapperType));
+        }
+      }
     }
+    return synthesizedWrappers;
   }
 
-  private void finalizeWrappers(
+  private Map<DexType, DexType> reverseWrapperMap() {
+    Map<DexType, DexType> reverseWrapperMap = new IdentityHashMap<>();
+    for (DexType type : typeWrappers.keySet()) {
+      reverseWrapperMap.put(typeWrappers.get(type), vivifiedTypeWrappers.get(type));
+    }
+    for (DexType type : vivifiedTypeWrappers.keySet()) {
+      reverseWrapperMap.put(vivifiedTypeWrappers.get(type), typeWrappers.get(type));
+    }
+    return reverseWrapperMap;
+  }
+
+  Map<DexProgramClass, DexProgramClass> synthesizeWrappersAndMapToReverse() {
+    Map<DexType, DexProgramClass> synthesizedWrappers = synthesizeWrappers();
+    Map<DexType, DexType> reverseMap = reverseWrapperMap();
+    Map<DexProgramClass, DexProgramClass> wrappersAndReverse = new IdentityHashMap<>();
+    for (DexProgramClass wrapper : synthesizedWrappers.values()) {
+      wrappersAndReverse.put(wrapper, synthesizedWrappers.get(reverseMap.get(wrapper.type)));
+    }
+    return wrappersAndReverse;
+  }
+
+  private void registerAndProcessWrappers(
+      DexApplication.Builder<?> builder,
       IRConverter irConverter,
       ExecutorService executorService,
-      Map<DexType, Pair<DexType, DexProgramClass>> wrappers,
-      BiConsumer<DexType, DexProgramClass> generateConversions)
+      Collection<DexProgramClass> wrappers)
       throws ExecutionException {
-    for (DexType type : wrappers.keySet()) {
-      DexProgramClass pgrmClass = wrappers.get(type).getSecond();
-      assert pgrmClass != null;
-      generateConversions.accept(type, pgrmClass);
-      irConverter.optimizeSynthesizedClass(pgrmClass, executorService);
+    for (DexProgramClass wrapper : wrappers) {
+      builder.addSynthesizedClass(wrapper, false);
+      appView.appInfo().addSynthesizedClass(wrapper);
     }
+    irConverter.optimizeSynthesizedClasses(wrappers, executorService);
   }
 
-  private boolean verifyAllClassesGenerated() {
-    for (Pair<DexType, DexProgramClass> pair : vivifiedTypeWrappers.values()) {
-      assert pair.getSecond() != null;
-    }
-    for (Pair<DexType, DexProgramClass> pair : typeWrappers.values()) {
-      assert pair.getSecond() != null;
-    }
-    return true;
+  private DexEncodedMethod generateTypeConversion(DexType type, DexType typeWrapperType) {
+    DexType reverse = vivifiedTypeWrappers.get(type);
+    return synthesizeConversionMethod(
+        typeWrapperType,
+        type,
+        type,
+        vivifiedTypeFor(type),
+        reverse == null ? null : wrappedValueField(reverse, vivifiedTypeFor(type)));
   }
 
-  private void registerSynthesizedClass(
-      DexProgramClass synthesizedClass, DexApplication.Builder<?> builder) {
-    builder.addSynthesizedClass(synthesizedClass, false);
-    appView.appInfo().addSynthesizedClass(synthesizedClass);
-  }
-
-  private void generateTypeConversions(DexType type, DexProgramClass synthesizedClass) {
-    Pair<DexType, DexProgramClass> reverse = vivifiedTypeWrappers.get(type);
-    assert reverse == null || reverse.getSecond() != null;
-    synthesizedClass.addDirectMethod(
-        synthesizeConversionMethod(
-            synthesizedClass.type,
-            type,
-            type,
-            vivifiedTypeFor(type),
-            reverse == null ? null : reverse.getSecond()));
-  }
-
-  private void generateVivifiedTypeConversions(DexType type, DexProgramClass synthesizedClass) {
-    Pair<DexType, DexProgramClass> reverse = typeWrappers.get(type);
-    synthesizedClass.addDirectMethod(
-        synthesizeConversionMethod(
-            synthesizedClass.type,
-            type,
-            vivifiedTypeFor(type),
-            type,
-            reverse == null ? null : reverse.getSecond()));
+  private DexEncodedMethod generateVivifiedTypeConversion(
+      DexType type, DexType vivifiedTypeWrapperType) {
+    DexType reverse = typeWrappers.get(type);
+    return synthesizeConversionMethod(
+        vivifiedTypeWrapperType,
+        type,
+        vivifiedTypeFor(type),
+        type,
+        reverse == null ? null : wrappedValueField(reverse, type));
   }
 
   private DexEncodedMethod synthesizeConversionMethod(
@@ -566,7 +609,7 @@
       DexType type,
       DexType argType,
       DexType returnType,
-      DexClass reverseWrapperClassOrNull) {
+      DexField reverseFieldOrNull) {
     DexMethod method =
         factory.createMethod(
             holder, factory.createProto(returnType, argType), factory.convertMethodName);
@@ -582,15 +625,11 @@
                   holder)
               .generateCfCode();
     } else {
-      DexField uniqueFieldOrNull =
-          reverseWrapperClassOrNull == null
-              ? null
-              : reverseWrapperClassOrNull.instanceFields().get(0).field;
       cfCode =
           new APIConverterWrapperConversionCfCodeProvider(
                   appView,
                   argType,
-                  uniqueFieldOrNull,
+                  reverseFieldOrNull,
                   factory.createField(holder, returnType, factory.wrapperFieldName))
               .generateCfCode();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 6a79659..0e8cbae 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -930,8 +930,7 @@
     // make original default methods abstract, remove bridge methods, create dispatch
     // classes if needed.
     AppInfo appInfo = appView.appInfo();
-    for (Entry<DexType, DexProgramClass> entry :
-        processInterfaces(builder, flavour).entrySet()) {
+    for (Entry<DexType, DexProgramClass> entry : processInterfaces(builder, flavour).entrySet()) {
       // Don't need to optimize synthesized class since all of its methods
       // are just moved from interfaces and don't need to be re-processed.
       DexProgramClass synthesizedClass = entry.getValue();
@@ -961,8 +960,7 @@
         && clazz.isInterface() == mustBeInterface;
   }
 
-  private Map<DexType, DexProgramClass> processInterfaces(
-      Builder<?> builder, Flavor flavour) {
+  private Map<DexType, DexProgramClass> processInterfaces(Builder<?> builder, Flavor flavour) {
     NestedGraphLense.Builder graphLensBuilder = InterfaceProcessorNestedGraphLense.builder();
     InterfaceProcessor processor = new InterfaceProcessor(appView, this);
     for (DexProgramClass clazz : builder.getProgramClasses()) {
@@ -1043,27 +1041,28 @@
     return false;
   }
 
-  public void warnMissingInterface(
-      DexClass classToDesugar, DexClass implementing, DexType missing) {
+  private boolean shouldIgnoreFromReports(DexType missing) {
+    return appView.rewritePrefix.hasRewrittenType(missing, appView)
+        || DesugaredLibraryWrapperSynthesizer.isSynthesizedWrapper(missing)
+        || DesugaredLibraryAPIConverter.isVivifiedType(missing)
+        || isCompanionClassType(missing)
+        || emulatedInterfaces.containsValue(missing)
+        || options.desugaredLibraryConfiguration.getCustomConversions().containsValue(missing);
+  }
+
+  void warnMissingInterface(DexClass classToDesugar, DexClass implementing, DexType missing) {
     // We use contains() on non hashed collection, but we know it's a 8 cases collection.
     // j$ interfaces won't be missing, they are in the desugared library.
-    if (!emulatedInterfaces.values().contains(missing)) {
-      options.warningMissingInterfaceForDesugar(classToDesugar, implementing, missing);
+    if (shouldIgnoreFromReports(missing)) {
+      return;
     }
+    options.warningMissingInterfaceForDesugar(classToDesugar, implementing, missing);
   }
 
   private void warnMissingType(DexMethod referencedFrom, DexType missing) {
     // Companion/Emulated interface/Conversion classes for desugared library won't be missing,
     // they are in the desugared library.
-    if (appView.rewritePrefix.hasRewrittenType(missing, appView)
-        || DesugaredLibraryWrapperSynthesizer.isSynthesizedWrapper(missing)
-        || isCompanionClassType(missing)
-        || appView
-            .options()
-            .desugaredLibraryConfiguration
-            .getCustomConversions()
-            .values()
-            .contains(missing)) {
+    if (shouldIgnoreFromReports(missing)) {
       return;
     }
     DexMethod method = appView.graphLense().getOriginalMethodSignature(referencedFrom);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 608fd4a..007f170 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -173,6 +173,7 @@
 
   /** Remove lambda deserialization methods. */
   public boolean removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes) {
+    boolean anyRemoved = false;
     for (DexProgramClass clazz : classes) {
       // Search for a lambda deserialization method and remove it if found.
       List<DexEncodedMethod> directMethods = clazz.directMethods();
@@ -185,14 +186,15 @@
             assert encoded.accessFlags.isStatic();
             assert encoded.accessFlags.isSynthetic();
             clazz.removeDirectMethod(i);
+            anyRemoved = true;
 
             // We assume there is only one such method in the class.
-            return true;
+            break;
           }
         }
       }
     }
-    return false;
+    return anyRemoved;
   }
 
   /** Generates lambda classes and adds them to the builder. */
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLense.java b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLense.java
index 78d5702..ded1538 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLense.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLense.java
@@ -75,12 +75,9 @@
   }
 
   @Override
-  public boolean isContextFreeForMethod(DexMethod method) {
-    if (!previousLense.isContextFreeForMethod(method)) {
-      return false;
-    }
-    DexMethod previous = previousLense.lookupMethod(method);
-    return !methodMap.containsKey(previous);
+  public boolean verifyIsContextFreeForMethod(DexMethod method) {
+    assert !methodMap.containsKey(previousLense.lookupMethod(method));
+    return true;
   }
 
   // This is true because mappings specific to this class can be filled.
@@ -145,11 +142,11 @@
       putFieldMap.put(from, to);
     }
 
-    public GraphLense build(AppView<?> appView, DexType nestConstructorType) {
+    public NestedPrivateMethodLense build(AppView<?> appView, DexType nestConstructorType) {
       assert typeMap.isEmpty();
       assert fieldMap.isEmpty();
       if (getFieldMap.isEmpty() && methodMap.isEmpty() && putFieldMap.isEmpty()) {
-        return appView.graphLense();
+        return null;
       }
       return new NestedPrivateMethodLense(
           appView, nestConstructorType, methodMap, getFieldMap, putFieldMap, appView.graphLense());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
index 1a6c365..c4c9bd7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -34,7 +33,8 @@
     super(appView);
   }
 
-  public GraphLense run(ExecutorService executorService, DexApplication.Builder<?> appBuilder)
+  public NestedPrivateMethodLense run(
+      ExecutorService executorService, DexApplication.Builder<?> appBuilder)
       throws ExecutionException {
     assert !appView.options().canUseNestBasedAccess()
         || appView.options().testing.enableForceNestBasedAccessDesugaringForTest;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index 744b10c..02cb581 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -179,6 +179,120 @@
         ImmutableList.of());
   }
 
+  public static CfCode CharSequenceMethods_compare(InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    CfLabel label6 = new CfLabel();
+    CfLabel label7 = new CfLabel();
+    CfLabel label8 = new CfLabel();
+    CfLabel label9 = new CfLabel();
+    CfLabel label10 = new CfLabel();
+    CfLabel label11 = new CfLabel();
+    CfLabel label12 = new CfLabel();
+    CfLabel label13 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        2,
+        8,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/CharSequence;"),
+                    options.itemFactory.createProto(options.itemFactory.createType("I")),
+                    options.itemFactory.createString("length")),
+                true),
+            new CfStore(ValueType.INT, 2),
+            label1,
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/CharSequence;"),
+                    options.itemFactory.createProto(options.itemFactory.createType("I")),
+                    options.itemFactory.createString("length")),
+                true),
+            new CfStore(ValueType.INT, 3),
+            label2,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfIfCmp(If.Type.NE, ValueType.OBJECT, label4),
+            label3,
+            new CfConstNumber(0, ValueType.INT),
+            new CfReturn(ValueType.INT),
+            label4,
+            new CfConstNumber(0, ValueType.INT),
+            new CfStore(ValueType.INT, 4),
+            label5,
+            new CfLoad(ValueType.INT, 2),
+            new CfLoad(ValueType.INT, 3),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Math;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("I"),
+                        options.itemFactory.createType("I"),
+                        options.itemFactory.createType("I")),
+                    options.itemFactory.createString("min")),
+                false),
+            new CfStore(ValueType.INT, 5),
+            label6,
+            new CfLoad(ValueType.INT, 4),
+            new CfLoad(ValueType.INT, 5),
+            new CfIfCmp(If.Type.GE, ValueType.INT, label12),
+            label7,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.INT, 4),
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/CharSequence;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("C"), options.itemFactory.createType("I")),
+                    options.itemFactory.createString("charAt")),
+                true),
+            new CfStore(ValueType.INT, 6),
+            label8,
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.INT, 4),
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/CharSequence;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("C"), options.itemFactory.createType("I")),
+                    options.itemFactory.createString("charAt")),
+                true),
+            new CfStore(ValueType.INT, 7),
+            label9,
+            new CfLoad(ValueType.INT, 6),
+            new CfLoad(ValueType.INT, 7),
+            new CfIfCmp(If.Type.EQ, ValueType.INT, label11),
+            label10,
+            new CfLoad(ValueType.INT, 6),
+            new CfLoad(ValueType.INT, 7),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
+            new CfReturn(ValueType.INT),
+            label11,
+            new CfIinc(4, 1),
+            new CfGoto(label6),
+            label12,
+            new CfLoad(ValueType.INT, 2),
+            new CfLoad(ValueType.INT, 3),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
+            new CfReturn(ValueType.INT),
+            label13),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
   public static CfCode CharacterMethods_compare(InternalOptions options, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BooleanMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BooleanMethodRewrites.java
index daf63b2..90654b2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BooleanMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BooleanMethodRewrites.java
@@ -13,10 +13,14 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.Xor;
 import java.util.List;
+import java.util.Set;
 
 public final class BooleanMethodRewrites {
   public static void rewriteLogicalAnd(
-      InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+      InvokeMethod invoke,
+      InstructionListIterator iterator,
+      DexItemFactory factory,
+      Set<Value> affectedValues) {
     List<Value> inValues = invoke.inValues();
     assert inValues.size() == 2;
 
@@ -25,7 +29,10 @@
   }
 
   public static void rewriteLogicalOr(
-      InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+      InvokeMethod invoke,
+      InstructionListIterator iterator,
+      DexItemFactory factory,
+      Set<Value> affectedValues) {
     List<Value> inValues = invoke.inValues();
     assert inValues.size() == 2;
 
@@ -34,7 +41,10 @@
   }
 
   public static void rewriteLogicalXor(
-      InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+      InvokeMethod invoke,
+      InstructionListIterator iterator,
+      DexItemFactory factory,
+      Set<Value> affectedValues) {
     List<Value> inValues = invoke.inValues();
     assert inValues.size() == 2;
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodRewrites.java
index 5f54256..695f51f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodRewrites.java
@@ -9,24 +9,35 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.Value;
 import java.util.Collections;
+import java.util.Set;
 
 public final class CollectionMethodRewrites {
 
   private CollectionMethodRewrites() {}
 
   public static void rewriteListOfEmpty(
-      InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+      InvokeMethod invoke,
+      InstructionListIterator iterator,
+      DexItemFactory factory,
+      Set<Value> affectedValues) {
     rewriteToCollectionMethod(invoke, iterator, factory, "emptyList");
   }
 
   public static void rewriteSetOfEmpty(
-      InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+      InvokeMethod invoke,
+      InstructionListIterator iterator,
+      DexItemFactory factory,
+      Set<Value> affectedValues) {
     rewriteToCollectionMethod(invoke, iterator, factory, "emptySet");
   }
 
   public static void rewriteMapOfEmpty(
-      InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+      InvokeMethod invoke,
+      InstructionListIterator iterator,
+      DexItemFactory factory,
+      Set<Value> affectedValues) {
     rewriteToCollectionMethod(invoke, iterator, factory, "emptyMap");
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/FloatMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/FloatMethodRewrites.java
index 9a4ef45..a5d557d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/FloatMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/FloatMethodRewrites.java
@@ -8,13 +8,18 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.Value;
+import java.util.Set;
 
 public final class FloatMethodRewrites {
 
   private FloatMethodRewrites() {}
 
   public static void rewriteHashCode(
-      InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+      InvokeMethod invoke,
+      InstructionListIterator iterator,
+      DexItemFactory factory,
+      Set<Value> affectedValues) {
     InvokeStatic mathInvoke = new InvokeStatic(
         factory.createMethod(factory.boxedFloatType, invoke.getInvokedMethod().proto,
             "floatToIntBits"), invoke.outValue(), invoke.inValues(), false);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/LongMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/LongMethodRewrites.java
index b842b71..348194b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/LongMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/LongMethodRewrites.java
@@ -12,13 +12,17 @@
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Value;
 import java.util.List;
+import java.util.Set;
 
 public final class LongMethodRewrites {
 
   private LongMethodRewrites() {}
 
   public static void rewriteCompare(
-      InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+      InvokeMethod invoke,
+      InstructionListIterator iterator,
+      DexItemFactory factory,
+      Set<Value> affectedValues) {
     List<Value> inValues = invoke.inValues();
     assert inValues.size() == 2;
     iterator.replaceCurrentInstruction(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
index 86d7494..b43abaa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
@@ -8,18 +8,25 @@
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Value;
 import java.util.List;
+import java.util.Set;
 
 public final class NumericMethodRewrites {
-  public static void rewriteToInvokeMath(InvokeMethod invoke, InstructionListIterator iterator,
-      DexItemFactory factory) {
+  public static void rewriteToInvokeMath(
+      InvokeMethod invoke,
+      InstructionListIterator iterator,
+      DexItemFactory factory,
+      Set<Value> affectedValues) {
     InvokeStatic mathInvoke = new InvokeStatic(
         factory.createMethod(factory.mathType, invoke.getInvokedMethod().proto,
             invoke.getInvokedMethod().name), invoke.outValue(), invoke.inValues(), false);
     iterator.replaceCurrentInstruction(mathInvoke);
   }
 
-  public static void rewriteToAddInstruction(InvokeMethod invoke, InstructionListIterator iterator,
-      DexItemFactory factory) {
+  public static void rewriteToAddInstruction(
+      InvokeMethod invoke,
+      InstructionListIterator iterator,
+      DexItemFactory factory,
+      Set<Value> affectedValues) {
     List<Value> values = invoke.inValues();
     assert values.size() == 2;
 
@@ -28,8 +35,11 @@
     iterator.replaceCurrentInstruction(add);
   }
 
-  public static void rewriteAsIdentity(InvokeMethod invoke, InstructionListIterator iterator,
-      DexItemFactory factory) {
+  public static void rewriteAsIdentity(
+      InvokeMethod invoke,
+      InstructionListIterator iterator,
+      DexItemFactory factory,
+      Set<Value> affectedValues) {
     List<Value> values = invoke.inValues();
     assert values.size() == 1;
     invoke.outValue().replaceUsers(values.get(0));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
index 83d32cc..911faa5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
@@ -11,11 +11,16 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Value;
+import java.util.Set;
 
 public final class ObjectsMethodRewrites {
 
   public static void rewriteToArraysHashCode(
-      InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+      InvokeMethod invoke,
+      InstructionListIterator iterator,
+      DexItemFactory factory,
+      Set<Value> affectedValues) {
     DexType arraysType = factory.createType(factory.arraysDescriptor);
     DexMethod hashCodeMethod =
         factory.createMethod(arraysType, invoke.getInvokedMethod().proto, "hashCode");
@@ -25,10 +30,14 @@
   }
 
   public static void rewriteRequireNonNull(
-      InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+      InvokeMethod invoke,
+      InstructionListIterator iterator,
+      DexItemFactory factory,
+      Set<Value> affectedValues) {
     InvokeVirtual getClass =
         new InvokeVirtual(factory.objectMethods.getClass, null, invoke.inValues());
-    if (invoke.outValue() != null) {
+    if (invoke.hasOutValue()) {
+      affectedValues.addAll(invoke.outValue().affectedValues());
       invoke.outValue().replaceUsers(invoke.inValues().get(0));
       invoke.setOutValue(null);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/OptionalMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/OptionalMethodRewrites.java
index 7965b0b..c10a292 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/OptionalMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/OptionalMethodRewrites.java
@@ -8,13 +8,18 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Value;
+import java.util.Set;
 
 public final class OptionalMethodRewrites {
 
   private OptionalMethodRewrites() {}
 
   public static void rewriteOrElseGet(
-      InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+      InvokeMethod invoke,
+      InstructionListIterator iterator,
+      DexItemFactory factory,
+      Set<Value> affectedValues) {
     InvokeVirtual getInvoke = new InvokeVirtual(
         factory.createMethod(factory.optionalType, invoke.getInvokedMethod().proto,
             "get"), invoke.outValue(), invoke.inValues());
@@ -22,7 +27,10 @@
   }
 
   public static void rewriteDoubleOrElseGet(
-      InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+      InvokeMethod invoke,
+      InstructionListIterator iterator,
+      DexItemFactory factory,
+      Set<Value> affectedValues) {
     InvokeVirtual getInvoke = new InvokeVirtual(
         factory.createMethod(factory.optionalDoubleType, invoke.getInvokedMethod().proto,
             "getAsDouble"), invoke.outValue(), invoke.inValues());
@@ -30,7 +38,10 @@
   }
 
   public static void rewriteIntOrElseGet(
-      InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+      InvokeMethod invoke,
+      InstructionListIterator iterator,
+      DexItemFactory factory,
+      Set<Value> affectedValues) {
     InvokeVirtual getInvoke = new InvokeVirtual(
         factory.createMethod(factory.optionalIntType, invoke.getInvokedMethod().proto,
             "getAsInt"), invoke.outValue(), invoke.inValues());
@@ -38,7 +49,10 @@
   }
 
   public static void rewriteLongOrElseGet(
-      InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) {
+      InvokeMethod invoke,
+      InstructionListIterator iterator,
+      DexItemFactory factory,
+      Set<Value> affectedValues) {
     InvokeVirtual getInvoke = new InvokeVirtual(
         factory.createMethod(factory.optionalLongType, invoke.getInvokedMethod().proto,
             "getAsLong"), invoke.outValue(), invoke.inValues());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
index b19e557..da20118 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
@@ -200,7 +200,7 @@
    *   // method with "assert xxx";
    *   void m() {
    *     if (!$assertionsDisabled) {
-   *       if (xxx) {
+   *       if (!xxx) {
    *         throw new AssertionError(...);
    *       }
    *     }
@@ -208,7 +208,8 @@
    * }
    * </pre>
    *
-   * With the rewriting below (and other rewritings) the resulting code is:
+   * With the rewriting below and AssertionTransformation.DISABLE (and other rewritings) the
+   * resulting code is:
    *
    * <pre>
    * class A {
@@ -216,6 +217,80 @@
    *   }
    * }
    * </pre>
+   *
+   * With AssertionTransformation.ENABLE (and other rewritings) the resulting code is:
+   *
+   * <pre>
+   * class A {
+   *   static boolean $assertionsDisabled;
+   *   void m() {
+   *     if (!xxx) {
+   *       throw new AssertionError(...);
+   *     }
+   *   }
+   * }
+   * </pre>
+   * For Kotlin the Class instance method desiredAssertionStatus() is only called for the class
+   * kotlin._Assertions, where kotlin._Assertions.class.desiredAssertionStatus() is read into the
+   * static field kotlin._Assertions.ENABLED.
+   *
+   * <pre>
+   * class _Assertions {
+   *   public static boolean ENABLED = _Assertions.class.desiredAssertionStatus();
+   * }
+   * </pre>
+   *
+   * <p>(actual code
+   * https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/jvm/src/kotlin/util/AssertionsJVM.kt)
+   *
+   * <p>The class:
+   *
+   * <pre>
+   * class A {
+   *   void m() {
+   *     assert(xxx)
+   *   }
+   * }
+   * </pre>
+   *
+   * Is compiled into:
+   *
+   * <pre>
+   * class A {
+   *   void m() {
+   *     if (!xxx) {
+   *       if (kotlin._Assertions.ENABLED) {
+   *         throw new AssertionError("Assertion failed")
+   *       }
+   *     }
+   *   }
+   * }
+   * </pre>
+   *
+   * With the rewriting below and AssertionTransformation.DISABLE (and other rewritings) the resulting code is:
+   *
+   * <pre>
+   * class A {
+   *   void m() {
+   *     if (!xxx) {}
+   *   }
+   * }
+   * </pre>
+   *
+   * With AssertionTransformation.ENABLE (and other rewritings) the resulting code is:
+   *
+   * <pre>
+   * class A {
+   *   void m() {
+   *     if (!xxx) {
+   *       throw new AssertionError("Assertion failed")
+   *     }
+   *   }
+   * }
+   * </pre>
+
+   * NOTE: that in Kotlin the assertion condition is always calculated. So it is still present in
+   * the code and even for AssertionTransformation.DISABLE.
    */
   public void run(DexEncodedMethod method, IRCode code, Timing timing) {
     if (enabled) {
@@ -242,7 +317,7 @@
       }
       clinit = clazz.getClassInitializer();
     }
-    if (clinit == null || !clinit.getOptimizationInfo().isInitializerEnablingJavaAssertions()) {
+    if (clinit == null || !clinit.getOptimizationInfo().isInitializerEnablingJavaVmAssertions()) {
       return;
     }
 
@@ -253,7 +328,11 @@
       if (current.isInvokeMethod()) {
         InvokeMethod invoke = current.asInvokeMethod();
         if (invoke.getInvokedMethod() == dexItemFactory.classMethods.desiredAssertionStatus) {
-          iterator.replaceCurrentInstruction(code.createIntConstant(0));
+          if (method.method.holder == dexItemFactory.kotlin.kotlinAssertions) {
+            rewriteKotlinAssertionEnable(code, transformation, iterator, invoke);
+          } else {
+            iterator.replaceCurrentInstruction(code.createIntConstant(0));
+          }
         }
       } else if (current.isStaticPut()) {
         StaticPut staticPut = current.asStaticPut();
@@ -269,4 +348,41 @@
       }
     }
   }
+
+  private void rewriteKotlinAssertionEnable(
+      IRCode code,
+      AssertionTransformation transformation,
+      InstructionListIterator iterator,
+      InvokeMethod invoke) {
+    if (iterator.hasNext() && transformation == AssertionTransformation.DISABLE) {
+      // Check if the invocation of Class.desiredAssertionStatus() is followed by a static
+      // put to kotlin._Assertions.ENABLED, and if so remove both instructions.
+      // See comment in ClassInitializerAssertionEnablingAnalysis for the expected instruction
+      // sequence.
+      Instruction nextInstruction = iterator.next();
+      if (nextInstruction.isStaticPut()
+          && nextInstruction.asStaticPut().getField().holder
+              == dexItemFactory.kotlin.kotlinAssertions
+          && nextInstruction.asStaticPut().getField().name == dexItemFactory.enabledFieldName
+          && invoke.outValue().numberOfUsers() == 1
+          && invoke.outValue().numberOfPhiUsers() == 0
+          && invoke.outValue().singleUniqueUser() == nextInstruction) {
+        iterator.removeOrReplaceByDebugLocalRead();
+        Instruction prevInstruction = iterator.previous();
+        assert prevInstruction == invoke;
+        iterator.removeOrReplaceByDebugLocalRead();
+      } else {
+        Instruction instruction = iterator.previous();
+        assert instruction == nextInstruction;
+        instruction = iterator.previous();
+        assert instruction == invoke;
+        instruction = iterator.next();
+        assert instruction == invoke;
+        iterator.replaceCurrentInstruction(code.createIntConstant(0));
+      }
+    } else {
+      iterator.replaceCurrentInstruction(
+          code.createIntConstant(transformation == AssertionTransformation.ENABLE ? 1 : 0));
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
index 73214ba..0ebe028 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
@@ -84,7 +84,7 @@
     }
   }
 
-  public void removeMarkedInstructions(Set<BasicBlock> blocksToBeRemoved) {
+  public AssumeDynamicTypeRemover removeMarkedInstructions(Set<BasicBlock> blocksToBeRemoved) {
     if (!assumeDynamicTypeInstructionsToRemove.isEmpty()) {
       for (BasicBlock block : code.blocks) {
         if (blocksToBeRemoved.contains(block)) {
@@ -99,6 +99,7 @@
         }
       }
     }
+    return this;
   }
 
   public void finish() {
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 51fe0fe..55db2fd 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
@@ -1177,11 +1177,13 @@
   }
 
   // Replace result uses for methods where something is known about what is returned.
-  public void rewriteMoveResult(IRCode code) {
-    if (options.isGeneratingClassFiles()) {
-      return;
+  public boolean rewriteMoveResult(IRCode code) {
+    if (options.isGeneratingClassFiles() || !code.metadata().mayHaveInvokeMethod()) {
+      return false;
     }
+
     AssumeDynamicTypeRemover assumeDynamicTypeRemover = new AssumeDynamicTypeRemover(appView, code);
+    boolean changed = false;
     boolean mayHaveRemovedTrivialPhi = false;
     Set<Value> affectedValues = Sets.newIdentityHashSet();
     Set<BasicBlock> blocksToBeRemoved = Sets.newIdentityHashSet();
@@ -1191,74 +1193,39 @@
       if (blocksToBeRemoved.contains(block)) {
         continue;
       }
+
       InstructionListIterator iterator = block.listIterator(code);
       while (iterator.hasNext()) {
-        Instruction current = iterator.next();
-        if (current.isInvokeMethod()) {
-          InvokeMethod invoke = current.asInvokeMethod();
-          Value outValue = invoke.outValue();
-          // TODO(b/124246610): extend to other variants that receive error messages or supplier.
-          if (invoke.getInvokedMethod() == dexItemFactory.objectsMethods.requireNonNull) {
-            Value obj = invoke.arguments().get(0);
-            if ((outValue == null && obj.hasLocalInfo())
-                || (outValue != null && !obj.hasSameOrNoLocal(outValue))) {
-              continue;
-            }
-            Nullability nullability = obj.getTypeLattice().nullability();
-            if (nullability.isDefinitelyNotNull()) {
-              if (outValue != null) {
-                affectedValues.addAll(outValue.affectedValues());
-                mayHaveRemovedTrivialPhi |= outValue.numberOfPhiUsers() > 0;
-                outValue.replaceUsers(obj);
-              }
-              iterator.removeOrReplaceByDebugLocalRead();
-            } else if (obj.isAlwaysNull(appView) && appView.appInfo().hasSubtyping()) {
-              iterator.replaceCurrentInstructionWithThrowNull(
-                  appView.withSubtyping(), code, blockIterator, blocksToBeRemoved, affectedValues);
-            }
-          } else if (outValue != null && !outValue.hasLocalInfo()) {
-            if (appView
-                .dexItemFactory()
-                .libraryMethodsReturningReceiver
-                .contains(invoke.getInvokedMethod())) {
-              if (checkArgumentType(invoke, 0)) {
-                affectedValues.addAll(outValue.affectedValues());
-                assumeDynamicTypeRemover.markUsersForRemoval(invoke.outValue());
-                mayHaveRemovedTrivialPhi |= outValue.numberOfPhiUsers() > 0;
-                outValue.replaceUsers(invoke.arguments().get(0));
-                invoke.setOutValue(null);
-              }
-            } else if (appView.appInfo().hasLiveness()) {
-              // Check if the invoked method is known to return one of its arguments.
-              DexEncodedMethod target =
-                  invoke.lookupSingleTarget(appView.withLiveness(), code.method.method.holder);
-              if (target != null && target.getOptimizationInfo().returnsArgument()) {
-                int argumentIndex = target.getOptimizationInfo().getReturnedArgument();
-                // Replace the out value of the invoke with the argument and ignore the out value.
-                if (argumentIndex >= 0 && checkArgumentType(invoke, argumentIndex)) {
-                  Value argument = invoke.arguments().get(argumentIndex);
-                  assert outValue.verifyCompatible(argument.outType());
-                  // Make sure that we are only narrowing information here. Note, in cases where
-                  // we cannot find the definition of types, computing lessThanOrEqual will
-                  // return false unless it is object.
-                  if (argument
-                      .getTypeLattice()
-                      .lessThanOrEqual(outValue.getTypeLattice(), appView)) {
-                    affectedValues.addAll(outValue.affectedValues());
-                    assumeDynamicTypeRemover.markUsersForRemoval(outValue);
-                    mayHaveRemovedTrivialPhi |= outValue.numberOfPhiUsers() > 0;
-                    outValue.replaceUsers(argument);
-                    invoke.setOutValue(null);
-                  }
-                }
-              }
+        InvokeMethod invoke = iterator.next().asInvokeMethod();
+        if (invoke == null || !invoke.hasOutValue() || invoke.outValue().hasLocalInfo()) {
+          continue;
+        }
+
+        // Check if the invoked method is known to return one of its arguments.
+        DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.method.method.holder);
+        if (target != null && target.getOptimizationInfo().returnsArgument()) {
+          int argumentIndex = target.getOptimizationInfo().getReturnedArgument();
+          // Replace the out value of the invoke with the argument and ignore the out value.
+          if (argumentIndex >= 0 && checkArgumentType(invoke, argumentIndex)) {
+            Value argument = invoke.arguments().get(argumentIndex);
+            Value outValue = invoke.outValue();
+            assert outValue.verifyCompatible(argument.outType());
+            // Make sure that we are only narrowing information here. Note, in cases where
+            // we cannot find the definition of types, computing lessThanOrEqual will
+            // return false unless it is object.
+            if (argument.getTypeLattice().lessThanOrEqual(outValue.getTypeLattice(), appView)) {
+              affectedValues.addAll(outValue.affectedValues());
+              assumeDynamicTypeRemover.markUsersForRemoval(outValue);
+              mayHaveRemovedTrivialPhi |= outValue.numberOfPhiUsers() > 0;
+              outValue.replaceUsers(argument);
+              invoke.setOutValue(null);
+              changed = true;
             }
           }
         }
       }
     }
-    assumeDynamicTypeRemover.removeMarkedInstructions(blocksToBeRemoved);
-    assumeDynamicTypeRemover.finish();
+    assumeDynamicTypeRemover.removeMarkedInstructions(blocksToBeRemoved).finish();
     if (!blocksToBeRemoved.isEmpty()) {
       code.removeBlocks(blocksToBeRemoved);
       code.removeAllTrivialPhis(affectedValues);
@@ -1270,6 +1237,7 @@
       new TypeAnalysis(appView).narrowing(affectedValues);
     }
     assert code.isConsistentSSA();
+    return changed;
   }
 
   enum RemoveCheckCastInstructionIfTrivialResult {
@@ -2160,15 +2128,6 @@
 
   public void simplifyDebugLocals(IRCode code) {
     for (BasicBlock block : code.blocks) {
-      for (Phi phi : block.getPhis()) {
-        if (!phi.hasLocalInfo() && phi.numberOfUsers() == 1 && phi.numberOfAllUsers() == 1) {
-          Instruction instruction = phi.singleUniqueUser();
-          if (instruction.isDebugLocalWrite()) {
-            removeDebugWriteOfPhi(code, phi, instruction.asDebugLocalWrite());
-          }
-        }
-      }
-
       InstructionListIterator iterator = block.listIterator(code);
       while (iterator.hasNext()) {
         Instruction prevInstruction = iterator.peekPrevious();
@@ -2202,29 +2161,6 @@
     }
   }
 
-  private void removeDebugWriteOfPhi(IRCode code, Phi phi, DebugLocalWrite write) {
-    assert write.src() == phi;
-    InstructionListIterator iterator = phi.getBlock().listIterator(code);
-    while (iterator.hasNext()) {
-      Instruction next = iterator.next();
-      if (!next.isDebugLocalWrite()) {
-        // If the debug write is not in the block header bail out.
-        return;
-      }
-      if (next == write) {
-        // Associate the phi with the local.
-        phi.setLocalInfo(write.getLocalInfo());
-        // Replace uses of the write with the phi.
-        write.outValue().replaceUsers(phi);
-        // Safely remove the write.
-        // TODO(zerny): Once phis become instructions, move debug values there instead of a nop.
-        iterator.removeOrReplaceByDebugLocalRead();
-        return;
-      }
-      assert next.getLocalInfo().name != write.getLocalInfo().name;
-    }
-  }
-
   private static class CSEExpressionEquivalence extends Equivalence<Instruction> {
 
     private final InternalOptions options;
@@ -2507,7 +2443,7 @@
           } else {
             DexType context = code.method.method.holder;
             AbstractValue abstractValue = lhs.getAbstractValue(appView, context);
-            if (abstractValue.isSingleEnumValue()) {
+            if (abstractValue.isSingleConstClassValue() || abstractValue.isSingleFieldValue()) {
               AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context);
               if (abstractValue == otherAbstractValue) {
                 simplifyIfWithKnownCondition(code, block, theIf, 0);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index f94b7a7..ef9c66c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayDeque;
 import java.util.Collection;
@@ -34,12 +35,15 @@
     this.codeRewriter = codeRewriter;
   }
 
-  public void run(IRCode code) {
-    removeUnneededCatchHandlers(code);
+  public void run(IRCode code, Timing timing) {
+    timing.begin("Remove dead code");
+
+    codeRewriter.rewriteMoveResult(code);
+
+    // We may encounter unneeded catch handlers after each iteration, e.g., if a dead instruction
+    // is the only throwing instruction in a block. Removing unneeded catch handlers can lead to
+    // more dead instructions.
     Deque<BasicBlock> worklist = new ArrayDeque<>();
-    // We may encounter unneeded catch handlers again, e.g., if a dead instruction (due to
-    // const-string canonicalization for example) is the only throwing instruction in a block.
-    // Removing unneeded catch handlers can lead to more dead instructions.
     do {
       worklist.addAll(code.topologicallySortedBlocks());
       while (!worklist.isEmpty()) {
@@ -49,7 +53,26 @@
       }
     } while (removeUnneededCatchHandlers(code));
     assert code.isConsistentSSA();
-    codeRewriter.rewriteMoveResult(code);
+
+    timing.end();
+  }
+
+  public boolean verifyNoDeadCode(IRCode code) {
+    assert !codeRewriter.rewriteMoveResult(code);
+    assert !removeUnneededCatchHandlers(code);
+    for (BasicBlock block : code.blocks) {
+      assert !block.hasDeadPhi(appView, code);
+      for (Instruction instruction : block.getInstructions()) {
+        // No unused move-result instructions.
+        assert !instruction.isInvoke()
+            || !instruction.hasOutValue()
+            || instruction.outValue().hasAnyUsers();
+        // No dead instructions.
+        assert !instruction.canBeDeadCode(appView, code)
+            || (instruction.hasOutValue() && !instruction.outValue().isDead(appView, code));
+      }
+    }
+    return true;
   }
 
   // Add the block from where the value originates to the worklist.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
index 88166e5..c38b7b0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.FieldInstruction;
 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.InvokeMethod;
 import com.android.tools.r8.ir.code.Phi;
@@ -250,6 +251,27 @@
       return Reason.ELIGIBLE;
     }
 
+    // An If using enum as inValue is valid if it matches e == null
+    // or e == X with X of same enum type as e. Ex: if (e == MyEnum.A).
+    if (instruction.isIf()) {
+      If anIf = instruction.asIf();
+      assert (anIf.getType() == If.Type.EQ || anIf.getType() == If.Type.NE)
+          : "Comparing a reference with " + anIf.getType().toString();
+      // e == null.
+      if (anIf.isZeroTest()) {
+        return Reason.ELIGIBLE;
+      }
+      // e == MyEnum.X
+      TypeLatticeElement leftType = anIf.lhs().getTypeLattice();
+      TypeLatticeElement rightType = anIf.rhs().getTypeLattice();
+      if (leftType.equalUpToNullability(rightType)) {
+        assert leftType.isClassType();
+        assert leftType.asClassTypeLatticeElement().getClassType() == enumClass.type;
+        return Reason.ELIGIBLE;
+      }
+      return Reason.INVALID_IF_TYPES;
+    }
+
     if (instruction.isAssume()) {
       Value outValue = instruction.outValue();
       return validateEnumUsages(code, outValue.uniqueUsers(), outValue.uniquePhiUsers(), enumClass);
@@ -318,6 +340,7 @@
     INVALID_FIELD_PUT,
     FIELD_PUT_ON_ENUM,
     TYPE_MISSMATCH_FIELD_PUT,
+    INVALID_IF_TYPES,
     OTHER_UNSUPPORTED_INSTRUCTION;
   }
 }
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 5bbc662..7618cb6 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
@@ -26,12 +26,13 @@
 import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
 import com.android.tools.r8.ir.code.ConstClass;
 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.Invoke;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Monitor;
 import com.android.tools.r8.ir.code.MoveException;
 import com.android.tools.r8.ir.code.Phi;
@@ -57,6 +58,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.ListUtils;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
@@ -605,39 +607,7 @@
           shouldSynthesizeMonitorEnterExit && !target.isStatic();
       if (shouldSynthesizeNullCheckForReceiver
           && !isSynthesizingNullCheckForReceiverUsingMonitorEnter) {
-        List<Value> arguments = code.collectArguments();
-        if (!arguments.isEmpty()) {
-          Value receiver = arguments.get(0);
-          assert receiver.isThis();
-
-          BasicBlock entryBlock = code.entryBlock();
-
-          // Insert a new block between the last argument instruction and the first actual
-          // instruction of the method.
-          BasicBlock throwBlock =
-              entryBlock.listIterator(code, arguments.size()).split(code, 0, null);
-          assert !throwBlock.hasCatchHandlers();
-
-          // Link the entry block to the successor of the newly inserted block.
-          BasicBlock continuationBlock = throwBlock.unlinkSingleSuccessor();
-          entryBlock.link(continuationBlock);
-
-          // Replace the last instruction of the entry block, which is now a goto instruction,
-          // with an `if-eqz` instruction that jumps to the newly inserted block if the receiver
-          // is null.
-          If ifInstruction = new If(If.Type.EQ, receiver);
-          entryBlock.replaceLastInstruction(ifInstruction, code);
-          assert ifInstruction.getTrueTarget() == throwBlock;
-          assert ifInstruction.fallthroughBlock() == continuationBlock;
-
-          // Replace the single goto instruction in the newly inserted block by `throw null`.
-          InstructionListIterator iterator = throwBlock.listIterator(code);
-          Value nullValue = iterator.insertConstNullInstruction(code, appView.options());
-          iterator.next();
-          iterator.replaceCurrentInstruction(new Throw(nullValue));
-        } else {
-          assert false : "Unable to synthesize a null check for the receiver";
-        }
+        synthesizeNullCheckForReceiver(appView, code);
       }
 
       // Insert monitor-enter and monitor-exit instructions if the method is synchronized.
@@ -757,6 +727,34 @@
       assert code.isConsistentSSA();
       return new InlineeWithReason(code, reason);
     }
+
+    private void synthesizeNullCheckForReceiver(AppView<?> appView, IRCode code) {
+      List<Value> arguments = code.collectArguments();
+      if (!arguments.isEmpty()) {
+        Value receiver = arguments.get(0);
+        assert receiver.isThis();
+
+        BasicBlock entryBlock = code.entryBlock();
+
+        // Insert a new block between the last argument instruction and the first actual
+        // instruction of the method.
+        BasicBlock throwBlock =
+            entryBlock.listIterator(code, arguments.size()).split(code, 0, null);
+        assert !throwBlock.hasCatchHandlers();
+
+        InstructionListIterator iterator = throwBlock.listIterator(code);
+        iterator.setInsertionPosition(entryBlock.exit().getPosition());
+        if (appView.options().canUseRequireNonNull()) {
+          DexMethod requireNonNullMethod = appView.dexItemFactory().objectsMethods.requireNonNull;
+          iterator.add(new InvokeStatic(requireNonNullMethod, null, ImmutableList.of(receiver)));
+        } else {
+          DexMethod getClassMethod = appView.dexItemFactory().objectMethods.getClass;
+          iterator.add(new InvokeVirtual(getClassMethod, null, ImmutableList.of(receiver)));
+        }
+      } else {
+        assert false : "Unable to synthesize a null check for the receiver";
+      }
+    }
   }
 
   static class InlineeWithReason {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index e50011a..a4c70be 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -443,7 +443,7 @@
     // For each of the actual potential targets, derive constraints based on the accessibility
     // of the method itself.
     Collection<DexEncodedMethod> targets =
-        resolutionResult.lookupVirtualDispatchTargets(isInterface, appView.appInfo());
+        resolutionResult.lookupVirtualDispatchTargets(appView.appInfo());
     for (DexEncodedMethod target : targets) {
       methodHolder = graphLense.lookupType(target.method.holder);
       assert appView.definitionFor(methodHolder) != null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
index 8b74c30..aeedc02 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
@@ -3,12 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Descriptor;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Equivalence;
@@ -31,11 +31,10 @@
 public abstract class MemberPoolCollection<T extends Descriptor> {
 
   final Equivalence<T> equivalence;
-  final AppView<? extends AppInfoWithSubtyping> appView;
+  final AppView<AppInfoWithLiveness> appView;
   final Map<DexClass, MemberPool<T>> memberPools = new ConcurrentHashMap<>();
 
-  MemberPoolCollection(
-      AppView<? extends AppInfoWithSubtyping> appView, Equivalence<T> equivalence) {
+  MemberPoolCollection(AppView<AppInfoWithLiveness> appView, Equivalence<T> equivalence) {
     this.appView = appView;
     this.equivalence = equivalence;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
index 46e0256..04b2874 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
@@ -4,12 +4,12 @@
 
 package com.android.tools.r8.ir.optimize;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 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.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.google.common.base.Predicates;
 import java.util.function.Predicate;
@@ -33,13 +33,13 @@
 
   private final Predicate<DexEncodedMethod> methodTester;
 
-  public MethodPoolCollection(AppView<? extends AppInfoWithSubtyping> appView) {
+  public MethodPoolCollection(AppView<AppInfoWithLiveness> appView) {
     super(appView, MethodSignatureEquivalence.get());
     this.methodTester = Predicates.alwaysTrue();
   }
 
   public MethodPoolCollection(
-      AppView<? extends  AppInfoWithSubtyping> appView, Predicate<DexEncodedMethod> methodTester) {
+      AppView<AppInfoWithLiveness> appView, Predicate<DexEncodedMethod> methodTester) {
     super(appView, MethodSignatureEquivalence.get());
     this.methodTester = methodTester;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
index c0a67fc..958749e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
@@ -94,15 +94,15 @@
         }
       }
 
-      // Each block with one or more catch handlers may have at most one throwing instruction.
-      if (instruction.instructionTypeCanThrow() && block.hasCatchHandlers()) {
-        return;
-      }
+      if (instruction.instructionTypeCanThrow()) {
+        // Each block with one or more catch handlers may have at most one throwing instruction.
+        if (block.hasCatchHandlers()) {
+          return;
+        }
 
-      // If the instruction can throw and one of the normal successor blocks has a catch handler,
-      // then we cannot merge the instruction into the predecessor, since this would change the
-      // exceptional control flow.
-      if (instruction.instructionInstanceCanThrow()) {
+        // If the instruction can throw and one of the normal successor blocks has a catch handler,
+        // then we cannot merge the instruction into the predecessor, since this would change the
+        // exceptional control flow.
         for (BasicBlock normalSuccessor : normalSuccessors) {
           if (normalSuccessor.hasCatchHandlers()) {
             return;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index b871461..c234bc3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
@@ -62,7 +61,7 @@
     DISALLOW_ARGUMENT_REMOVAL
   }
 
-  static class UninstantiatedTypeOptimizationGraphLense extends NestedGraphLense {
+  public static class UninstantiatedTypeOptimizationGraphLense extends NestedGraphLense {
 
     private final Map<DexMethod, RemovedArgumentsInfo> removedArgumentsInfoPerMethod;
 
@@ -116,9 +115,8 @@
     this.typeChecker = new TypeChecker(appView);
   }
 
-  public GraphLense run(
+  public UninstantiatedTypeOptimizationGraphLense run(
       MethodPoolCollection methodPoolCollection, ExecutorService executorService, Timing timing) {
-
     try {
       methodPoolCollection.buildAll(executorService, timing);
     } catch (Exception e) {
@@ -144,7 +142,7 @@
       return new UninstantiatedTypeOptimizationGraphLense(
           methodMapping, removedArgumentsInfoPerMethod, appView);
     }
-    return appView.graphLense();
+    return null;
   }
 
   private void processClass(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index 705b2bd..d8568d5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -53,7 +53,7 @@
   private final BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
   private final Map<DexMethod, RemovedArgumentsInfo> removedArguments = new IdentityHashMap<>();
 
-  static class UnusedArgumentsGraphLense extends NestedGraphLense {
+  public static class UnusedArgumentsGraphLense extends NestedGraphLense {
 
     private final Map<DexMethod, RemovedArgumentsInfo> removedArguments;
 
@@ -95,7 +95,8 @@
     this.methodPoolCollection = methodPoolCollection;
   }
 
-  public GraphLense run(ExecutorService executorService, Timing timing) throws ExecutionException {
+  public UnusedArgumentsGraphLense run(ExecutorService executorService, Timing timing)
+      throws ExecutionException {
     ThreadUtils.awaitFutures(
         Streams.stream(appView.appInfo().classes())
             .map(this::runnableForClass)
@@ -122,7 +123,7 @@
           removedArguments);
     }
 
-    return appView.graphLense();
+    return null;
   }
 
   private class UsedSignatures {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index a5b8d52..9231d05 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.classinliner;
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.google.common.base.Predicates.alwaysFalse;
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
@@ -84,6 +85,7 @@
       AssumeAndCheckCastAliasedValueConfiguration.getInstance();
 
   private final AppView<AppInfoWithLiveness> appView;
+  private final DexItemFactory dexItemFactory;
   private final Inliner inliner;
   private final Function<DexClass, EligibilityStatus> isClassEligible;
   private final MethodProcessor methodProcessor;
@@ -117,6 +119,7 @@
       DexEncodedMethod method,
       Instruction root) {
     this.appView = appView;
+    this.dexItemFactory = appView.dexItemFactory();
     this.inliner = inliner;
     this.isClassEligible = isClassEligible;
     this.method = method;
@@ -259,6 +262,14 @@
           InvokeMethod invokeMethod = user.asInvokeMethod();
           DexEncodedMethod singleTarget =
               invokeMethod.lookupSingleTarget(appView, method.method.holder);
+          if (singleTarget == null) {
+            return user; // Not eligible.
+          }
+
+          if (isEligibleLibraryMethodCall(invokeMethod, singleTarget)) {
+            continue;
+          }
+
           if (!isEligibleSingleTarget(singleTarget)) {
             return user; // Not eligible.
           }
@@ -266,7 +277,7 @@
           // Eligible constructor call (for new instance roots only).
           if (user.isInvokeDirect()) {
             InvokeDirect invoke = user.asInvokeDirect();
-            if (appView.dexItemFactory().isConstructor(invoke.getInvokedMethod())) {
+            if (dexItemFactory.isConstructor(invoke.getInvokedMethod())) {
               boolean isCorrespondingConstructorCall =
                   root.isNewInstance()
                       && !invoke.inValues().isEmpty()
@@ -430,11 +441,11 @@
             }
 
             DexMethod invokedMethod = invoke.getInvokedMethod();
-            if (invokedMethod == appView.dexItemFactory().objectMethods.constructor) {
+            if (invokedMethod == dexItemFactory.objectMethods.constructor) {
               continue;
             }
 
-            if (!appView.dexItemFactory().isConstructor(invokedMethod)) {
+            if (!dexItemFactory.isConstructor(invokedMethod)) {
               throw new IllegalClassInlinerStateException();
             }
 
@@ -482,7 +493,7 @@
         if (instruction.isInvokeMethodWithReceiver()) {
           InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
           DexMethod invokedMethod = invoke.getInvokedMethod();
-          if (invokedMethod == appView.dexItemFactory().objectMethods.constructor) {
+          if (invokedMethod == dexItemFactory.objectMethods.constructor) {
             continue;
           }
 
@@ -535,14 +546,36 @@
   private void removeMiscUsages(IRCode code) {
     boolean needToRemoveUnreachableBlocks = false;
     for (Instruction user : eligibleInstance.uniqueUsers()) {
-      // Remove the call to java.lang.Object.<init>().
-      if (user.isInvokeDirect()) {
-        InvokeDirect invoke = user.asInvokeDirect();
-        if (root.isNewInstance()
-            && invoke.getInvokedMethod() == appView.dexItemFactory().objectMethods.constructor) {
+      if (user.isInvokeMethod()) {
+        InvokeMethod invoke = user.asInvokeMethod();
+
+        // Remove the call to java.lang.Object.<init>().
+        if (user.isInvokeDirect()) {
+          if (root.isNewInstance()
+              && invoke.getInvokedMethod() == dexItemFactory.objectMethods.constructor) {
+            removeInstruction(invoke);
+            continue;
+          }
+        }
+
+        if (user.isInvokeStatic()) {
+          assert invoke.getInvokedMethod() == dexItemFactory.objectsMethods.requireNonNull;
           removeInstruction(invoke);
           continue;
         }
+
+        DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.method.holder);
+        if (singleTarget != null) {
+          Predicate<InvokeMethod> noSideEffectsPredicate =
+              dexItemFactory.libraryMethodsWithoutSideEffects.getOrDefault(
+                  singleTarget.method, alwaysFalse());
+          if (noSideEffectsPredicate.test(invoke)) {
+            if (!invoke.hasOutValue() || !invoke.outValue().hasAnyUsers()) {
+              removeInstruction(invoke);
+              continue;
+            }
+          }
+        }
       }
 
       if (user.isIf()) {
@@ -672,7 +705,7 @@
 
   private InliningInfo isEligibleConstructorCall(
       InvokeDirect invoke, DexEncodedMethod singleTarget) {
-    assert appView.dexItemFactory().isConstructor(invoke.getInvokedMethod());
+    assert dexItemFactory.isConstructor(invoke.getInvokedMethod());
     assert isEligibleSingleTarget(singleTarget);
 
     // Must be a constructor called on the receiver.
@@ -704,7 +737,6 @@
     }
 
     // Check that the entire constructor chain can be inlined into the current context.
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
     DexMethod parent = instanceInitializerInfo.getParent();
     while (parent != dexItemFactory.objectMethods.constructor) {
       if (parent == null) {
@@ -873,6 +905,9 @@
     if (!isEligibleSingleTarget(singleTarget)) {
       return false;
     }
+    if (singleTarget.isLibraryMethodOverride().isTrue()) {
+      return false;
+    }
     return isEligibleVirtualMethodCall(
         null,
         invokedMethod,
@@ -934,8 +969,7 @@
   }
 
   private boolean isExtraMethodCall(InvokeMethod invoke) {
-    if (invoke.isInvokeDirect()
-        && appView.dexItemFactory().isConstructor(invoke.getInvokedMethod())) {
+    if (invoke.isInvokeDirect() && dexItemFactory.isConstructor(invoke.getInvokedMethod())) {
       return false;
     }
     if (invoke.isInvokeMethodWithReceiver()) {
@@ -1019,6 +1053,18 @@
     return true;
   }
 
+  private boolean isEligibleLibraryMethodCall(InvokeMethod invoke, DexEncodedMethod singleTarget) {
+    Predicate<InvokeMethod> noSideEffectsPredicate =
+        dexItemFactory.libraryMethodsWithoutSideEffects.get(singleTarget.method);
+    if (noSideEffectsPredicate != null && noSideEffectsPredicate.test(invoke)) {
+      return !invoke.hasOutValue() || !invoke.outValue().hasAnyUsers();
+    }
+    if (singleTarget.method == dexItemFactory.objectsMethods.requireNonNull) {
+      return !invoke.hasOutValue() || !invoke.outValue().hasAnyUsers();
+    }
+    return false;
+  }
+
   private boolean isEligibleParameterUsages(
       InvokeMethod invoke,
       List<Value> arguments,
@@ -1123,7 +1169,7 @@
   }
 
   private boolean isInstanceInitializerEligibleForClassInlining(DexMethod method) {
-    if (method == appView.dexItemFactory().objectMethods.constructor) {
+    if (method == dexItemFactory.objectMethods.constructor) {
       return true;
     }
     DexEncodedMethod encodedMethod = appView.definitionFor(method);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 36ccecf..29fcf04 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -143,7 +143,7 @@
   }
 
   @Override
-  public boolean isInitializerEnablingJavaAssertions() {
+  public boolean isInitializerEnablingJavaVmAssertions() {
     return UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index 46749fd..b7b4973 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -60,7 +60,7 @@
 
   InstanceInitializerInfo getInstanceInitializerInfo();
 
-  boolean isInitializerEnablingJavaAssertions();
+  boolean isInitializerEnablingJavaVmAssertions();
 
   AbstractValue getAbstractReturnValue();
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index b97cc0d..d1a6c3a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -74,6 +74,7 @@
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeNewArray;
+import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Return;
@@ -224,6 +225,22 @@
             return;
           }
 
+        case INVOKE_STATIC:
+          {
+            InvokeStatic invoke = insn.asInvokeStatic();
+            DexEncodedMethod singleTarget =
+                invoke.lookupSingleTarget(appView, method.method.holder);
+            if (singleTarget == null) {
+              return; // Not allowed.
+            }
+            if (singleTarget.method == dexItemFactory.objectsMethods.requireNonNull) {
+              if (!invoke.hasOutValue() || !invoke.outValue().hasAnyUsers()) {
+                continue;
+              }
+            }
+            return;
+          }
+
         case INVOKE_VIRTUAL:
           {
             InvokeVirtual invoke = insn.asInvokeVirtual();
@@ -284,7 +301,7 @@
   }
 
   private ParameterUsage collectParameterUsages(int i, Value value) {
-    ParameterUsageBuilder builder = new ParameterUsageBuilder(value, i);
+    ParameterUsageBuilder builder = new ParameterUsageBuilder(value, i, dexItemFactory);
     for (Instruction user : value.aliasedUsers()) {
       if (!builder.note(user)) {
         return null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 27f8be0..0fec1f7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -235,7 +235,7 @@
   }
 
   @Override
-  public synchronized void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
+  public synchronized void setInitializerEnablingJavaVmAssertions(DexEncodedMethod method) {
     getMethodOptimizationInfoForUpdating(method).setInitializerEnablingJavaAssertions();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 8d6e8c4..ee5a745 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -113,8 +113,7 @@
       DexEncodedMethod method, InstanceInitializerInfo instanceInitializerInfo) {}
 
   @Override
-  public void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
-  }
+  public void setInitializerEnablingJavaVmAssertions(DexEncodedMethod method) {}
 
   @Override
   public void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 65ea424..ad3080b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -87,7 +87,7 @@
 
   @Override
   public void methodReturnsArgument(DexEncodedMethod method, int argument) {
-    // Ignored.
+    method.getMutableOptimizationInfo().markReturnsArgument(argument);
   }
 
   @Override
@@ -120,7 +120,7 @@
 
   @Override
   public void methodNeverReturnsNull(DexEncodedMethod method) {
-    // Ignored.
+    method.getMutableOptimizationInfo().neverReturnsNull();
   }
 
   @Override
@@ -162,7 +162,7 @@
   }
 
   @Override
-  public void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
+  public void setInitializerEnablingJavaVmAssertions(DexEncodedMethod method) {
     method.getMutableOptimizationInfo().setInitializerEnablingJavaAssertions();
   }
 
@@ -173,12 +173,12 @@
 
   @Override
   public void setNonNullParamOrThrow(DexEncodedMethod method, BitSet facts) {
-    // Ignored.
+    method.getMutableOptimizationInfo().setNonNullParamOrThrow(facts);
   }
 
   @Override
   public void setNonNullParamOnNormalExits(DexEncodedMethod method, BitSet facts) {
-    // Ignored.
+    method.getMutableOptimizationInfo().setNonNullParamOnNormalExits(facts);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
index e911307..921ae28 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.info;
 
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.If.Type;
@@ -12,6 +13,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.Monitor;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Value;
@@ -139,6 +141,8 @@
 
     private final int index;
     private final Value arg;
+    private final DexItemFactory dexItemFactory;
+
     private final Set<Type> ifZeroTestTypes = new HashSet<>();
     private final List<Pair<Invoke.Type, DexMethod>> callsOnReceiver = new ArrayList<>();
 
@@ -148,9 +152,10 @@
     private boolean isReturned = false;
     private boolean isUsedInMonitor = false;
 
-    ParameterUsageBuilder(Value arg, int index) {
+    ParameterUsageBuilder(Value arg, int index, DexItemFactory dexItemFactory) {
       this.arg = arg;
       this.index = index;
+      this.dexItemFactory = dexItemFactory;
     }
 
     // Returns false if the instruction is not supported.
@@ -172,6 +177,9 @@
       if (instruction.isInvokeMethodWithReceiver()) {
         return note(instruction.asInvokeMethodWithReceiver());
       }
+      if (instruction.isInvokeStatic()) {
+        return note(instruction.asInvokeStatic());
+      }
       if (instruction.isReturn()) {
         return note(instruction.asReturn());
       }
@@ -238,6 +246,16 @@
       return false;
     }
 
+    private boolean note(InvokeStatic invokeInstruction) {
+      if (invokeInstruction.getInvokedMethod() == dexItemFactory.objectsMethods.requireNonNull) {
+        if (!invokeInstruction.hasOutValue() || !invokeInstruction.outValue().hasAnyUsers()) {
+          ifZeroTestTypes.add(Type.EQ);
+          return true;
+        }
+      }
+      return false;
+    }
+
     private boolean note(Return returnInstruction) {
       assert returnInstruction.inValues().size() == 1
           && returnInstruction.inValues().get(0).getAliasedValue() == arg;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index dc67349..d2e78f4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -106,7 +106,7 @@
         BooleanUtils.intValue(defaultOptInfo.triggersClassInitBeforeAnySideEffect())
             * TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG;
     defaultFlags |=
-        BooleanUtils.intValue(defaultOptInfo.isInitializerEnablingJavaAssertions())
+        BooleanUtils.intValue(defaultOptInfo.isInitializerEnablingJavaVmAssertions())
             * INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG;
     defaultFlags |=
         BooleanUtils.intValue(defaultOptInfo.isReachabilitySensitive())
@@ -287,7 +287,7 @@
   }
 
   @Override
-  public boolean isInitializerEnablingJavaAssertions() {
+  public boolean isInitializerEnablingJavaVmAssertions() {
     return isFlagSet(INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
index edd657b..31444c6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.library;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -21,10 +20,10 @@
 
 public class BooleanMethodOptimizer implements LibraryMethodModelCollection {
 
-  private final AppView<? extends AppInfoWithSubtyping> appView;
+  private final AppView<?> appView;
   private final DexItemFactory dexItemFactory;
 
-  BooleanMethodOptimizer(AppView<? extends AppInfoWithSubtyping> appView) {
+  BooleanMethodOptimizer(AppView<?> appView) {
     this.appView = appView;
     this.dexItemFactory = appView.dexItemFactory();
   }
@@ -57,9 +56,9 @@
         StaticGet staticGet = definition.asStaticGet();
         DexField field = staticGet.getField();
         if (field == dexItemFactory.booleanMembers.TRUE) {
-          instructionIterator.replaceCurrentInstructionWithConstInt(appView, code, 1);
+          instructionIterator.replaceCurrentInstructionWithConstInt(code, 1);
         } else if (field == dexItemFactory.booleanMembers.FALSE) {
-          instructionIterator.replaceCurrentInstructionWithConstInt(appView, code, 0);
+          instructionIterator.replaceCurrentInstructionWithConstInt(code, 0);
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
index 67a7588..e2999df 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.library;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
@@ -29,9 +28,10 @@
   private final Map<DexType, LibraryMethodModelCollection> libraryMethodModelCollections =
       new IdentityHashMap<>();
 
-  public LibraryMethodOptimizer(AppView<? extends AppInfoWithSubtyping> appView) {
+  public LibraryMethodOptimizer(AppView<?> appView) {
     this.appView = appView;
     register(new BooleanMethodOptimizer(appView));
+    register(new ObjectsMethodOptimizer(appView));
     register(new StringMethodOptimizer(appView));
 
     if (LogMethodOptimizer.isEnabled(appView)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
new file mode 100644
index 0000000..050640c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2020, 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.library;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import java.util.BitSet;
+
+public class LibraryOptimizationInfoInitializer {
+
+  private final AppView<?> appView;
+  private final DexItemFactory dexItemFactory;
+
+  private final OptimizationFeedbackSimple feedback = OptimizationFeedbackSimple.getInstance();
+
+  public LibraryOptimizationInfoInitializer(AppView<?> appView) {
+    this.appView = appView;
+    this.dexItemFactory = appView.dexItemFactory();
+  }
+
+  public void run() {
+    modelLibraryMethodsReturningNonNull();
+    modelLibraryMethodsReturningReceiver();
+    modelRequireNonNullMethods();
+  }
+
+  private void modelLibraryMethodsReturningNonNull() {
+    for (DexMethod method : dexItemFactory.libraryMethodsReturningNonNull) {
+      DexEncodedMethod definition = appView.definitionFor(method);
+      if (definition != null) {
+        feedback.methodNeverReturnsNull(definition);
+      }
+    }
+  }
+
+  private void modelLibraryMethodsReturningReceiver() {
+    for (DexMethod method : dexItemFactory.libraryMethodsReturningReceiver) {
+      DexEncodedMethod definition = appView.definitionFor(method);
+      if (definition != null) {
+        feedback.methodReturnsArgument(definition, 0);
+      }
+    }
+  }
+
+  private void modelRequireNonNullMethods() {
+    for (DexMethod requireNonNullMethod : dexItemFactory.objectsMethods.requireNonNullMethods()) {
+      DexEncodedMethod definition = appView.definitionFor(requireNonNullMethod);
+      if (definition != null) {
+        feedback.methodReturnsArgument(definition, 0);
+
+        BitSet nonNullParamOrThrow = new BitSet();
+        nonNullParamOrThrow.set(0);
+        feedback.setNonNullParamOrThrow(definition, nonNullParamOrThrow);
+
+        BitSet nonNullParamOnNormalExits = new BitSet();
+        nonNullParamOnNormalExits.set(0);
+        feedback.setNonNullParamOnNormalExits(definition, nonNullParamOnNormalExits);
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
index f65b57a..6cd36b3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.library;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -15,6 +14,7 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.ProguardConfiguration;
 import java.util.Set;
 
 public class LogMethodOptimizer implements LibraryMethodModelCollection {
@@ -26,7 +26,7 @@
   private static final int ERROR = 6;
   private static final int ASSERT = 7;
 
-  private final AppView<? extends AppInfoWithSubtyping> appView;
+  private final AppView<?> appView;
 
   private final DexType logType;
 
@@ -38,7 +38,7 @@
   private final DexMethod eMethod;
   private final DexMethod wtfMethod;
 
-  LogMethodOptimizer(AppView<? extends AppInfoWithSubtyping> appView) {
+  LogMethodOptimizer(AppView<?> appView) {
     this.appView = appView;
 
     DexItemFactory dexItemFactory = appView.dexItemFactory();
@@ -89,7 +89,9 @@
   }
 
   public static boolean isEnabled(AppView<?> appView) {
-    return appView.options().getProguardConfiguration().getMaxRemovedAndroidLogLevel() >= VERBOSE;
+    ProguardConfiguration proguardConfiguration = appView.options().getProguardConfiguration();
+    return proguardConfiguration != null
+        && proguardConfiguration.getMaxRemovedAndroidLogLevel() >= VERBOSE;
   }
 
   @Override
@@ -146,7 +148,7 @@
   private void replaceInvokeWithConstNumber(
       IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke, int value) {
     if (invoke.hasOutValue() && invoke.outValue().hasAnyUsers()) {
-      instructionIterator.replaceCurrentInstructionWithConstInt(appView, code, value);
+      instructionIterator.replaceCurrentInstructionWithConstInt(code, value);
     } else {
       instructionIterator.removeOrReplaceByDebugLocalRead();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
new file mode 100644
index 0000000..975aca8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2020, 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.library;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import java.util.Set;
+
+public class ObjectsMethodOptimizer implements LibraryMethodModelCollection {
+
+  private final DexItemFactory dexItemFactory;
+
+  ObjectsMethodOptimizer(AppView<?> appView) {
+    this.dexItemFactory = appView.dexItemFactory();
+  }
+
+  @Override
+  public DexType getType() {
+    return dexItemFactory.objectsType;
+  }
+
+  @Override
+  public void optimize(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      DexEncodedMethod singleTarget,
+      Set<Value> affectedValues) {
+    if (dexItemFactory.objectsMethods.isRequireNonNullMethod(singleTarget.method)) {
+      optimizeRequireNonNull(instructionIterator, invoke, affectedValues);
+    }
+  }
+
+  private void optimizeRequireNonNull(
+      InstructionListIterator instructionIterator, InvokeMethod invoke, Set<Value> affectedValues) {
+    Value inValue = invoke.inValues().get(0);
+    if (inValue.getTypeLattice().isDefinitelyNotNull()) {
+      Value outValue = invoke.outValue();
+      if (outValue != null) {
+        affectedValues.addAll(outValue.affectedValues());
+        outValue.replaceUsers(inValue);
+      }
+      instructionIterator.removeOrReplaceByDebugLocalRead();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
index 5e4033b..501a761 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.library;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -20,10 +19,10 @@
 
 public class StringMethodOptimizer implements LibraryMethodModelCollection {
 
-  private final AppView<? extends AppInfoWithSubtyping> appView;
+  private final AppView<?> appView;
   private final DexItemFactory dexItemFactory;
 
-  StringMethodOptimizer(AppView<? extends AppInfoWithSubtyping> appView) {
+  StringMethodOptimizer(AppView<?> appView) {
     this.appView = appView;
     this.dexItemFactory = appView.dexItemFactory();
   }
@@ -53,7 +52,7 @@
       Value second = invoke.arguments().get(1).getAliasedValue();
       if (isPrunedClassNameComparison(first, second, context)
           || isPrunedClassNameComparison(second, first, context)) {
-        instructionIterator.replaceCurrentInstructionWithConstInt(appView, code, 0);
+        instructionIterator.replaceCurrentInstructionWithConstInt(code, 0);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 247f13e..e0ca9093 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -25,6 +25,8 @@
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
@@ -32,7 +34,6 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
@@ -49,6 +50,7 @@
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
@@ -60,8 +62,8 @@
   private final IRConverter converter;
 
   // Optimization order matters, hence a collection that preserves orderings.
-  private final Map<DexEncodedMethod, ImmutableList.Builder<Consumer<IRCode>>> processingQueue =
-      new IdentityHashMap<>();
+  private final Map<DexEncodedMethod, ImmutableList.Builder<BiConsumer<IRCode, MethodProcessor>>>
+      processingQueue = new IdentityHashMap<>();
 
   private final Set<DexEncodedMethod> referencingExtraMethods = Sets.newIdentityHashSet();
   private final Map<DexEncodedMethod, CandidateInfo> hostClassInits = new IdentityHashMap<>();
@@ -265,7 +267,7 @@
 
   private void enqueueMethodsWithCodeOptimizations(
       Iterable<DexEncodedMethod> methods,
-      Consumer<ImmutableList.Builder<Consumer<IRCode>>> extension) {
+      Consumer<ImmutableList.Builder<BiConsumer<IRCode, MethodProcessor>>> extension) {
     for (DexEncodedMethod method : methods) {
       extension.accept(processingQueue.computeIfAbsent(method, ignore -> ImmutableList.builder()));
     }
@@ -274,18 +276,20 @@
   /**
    * Processes the given methods concurrently using the given strategy.
    *
-   * <p>Note that, when the strategy {@link #rewriteReferences(IRCode)} is being applied, it is
-   * important that we never inline a method from `methods` which has still not been reprocessed.
-   * This could lead to broken code, because the strategy that rewrites the broken references is
-   * applied *before* inlining (because the broken references in the inlinee are never rewritten).
-   * We currently avoid this situation by processing all the methods concurrently
+   * <p>Note that, when the strategy {@link #rewriteReferences(IRCode, MethodProcessor)} is being
+   * applied, it is important that we never inline a method from `methods` which has still not been
+   * reprocessed. This could lead to broken code, because the strategy that rewrites the broken
+   * references is applied *before* inlining (because the broken references in the inlinee are never
+   * rewritten). We currently avoid this situation by processing all the methods concurrently
    * (inlining of a method that is processed concurrently is not allowed).
    */
   private void processMethodsConcurrently(
       OptimizationFeedback feedback, ExecutorService executorService) throws ExecutionException {
-    ThreadUtils.processItems(
-        processingQueue.keySet(),
-        method -> forEachMethod(method, processingQueue.get(method).build(), feedback),
+    Set<DexEncodedMethod> wave = processingQueue.keySet();
+    OneTimeMethodProcessor methodProcessor = OneTimeMethodProcessor.getInstance(wave);
+    methodProcessor.forEachWave(
+        method ->
+            forEachMethod(method, processingQueue.get(method).build(), feedback, methodProcessor),
         executorService);
     // TODO(b/140767158): No need to clear if we can do every thing in one go.
     processingQueue.clear();
@@ -294,25 +298,33 @@
   // TODO(b/140766440): Should be part or variant of PostProcessor.
   private void forEachMethod(
       DexEncodedMethod method,
-      Collection<Consumer<IRCode>> codeOptimizations,
-      OptimizationFeedback feedback) {
+      Collection<BiConsumer<IRCode, MethodProcessor>> codeOptimizations,
+      OptimizationFeedback feedback,
+      OneTimeMethodProcessor methodProcessor) {
     Origin origin = appView.appInfo().originFor(method.method.holder);
     IRCode code = method.buildIR(appView, origin);
-    codeOptimizations.forEach(codeOptimization -> codeOptimization.accept(code));
+    codeOptimizations.forEach(codeOptimization -> codeOptimization.accept(code, methodProcessor));
     CodeRewriter.removeAssumeInstructions(appView, code);
-    converter.finalizeIR(method, code, feedback, Timing.empty());
+    converter.removeDeadCodeAndFinalizeIR(method, code, feedback, Timing.empty());
   }
 
-  private void insertAssumeInstructions(IRCode code) {
+  private void insertAssumeInstructions(IRCode code, MethodProcessor methodProcessor) {
     CodeRewriter.insertAssumeInstructions(code, converter.assumers);
   }
 
-  private Consumer<IRCode> collectOptimizationInfo(OptimizationFeedback feedback) {
-    return code ->
-        converter.collectOptimizationInfo(code, ClassInitializerDefaultsResult.empty(), feedback);
+  private BiConsumer<IRCode, MethodProcessor> collectOptimizationInfo(
+      OptimizationFeedback feedback) {
+    return (code, methodProcessor) ->
+        converter.collectOptimizationInfo(
+            code.method,
+            code,
+            ClassInitializerDefaultsResult.empty(),
+            feedback,
+            methodProcessor,
+            Timing.empty());
   }
 
-  private void removeCandidateInstantiation(IRCode code) {
+  private void removeCandidateInstantiation(IRCode code, MethodProcessor methodProcessor) {
     CandidateInfo candidateInfo = hostClassInits.get(code.method);
     assert candidateInfo != null;
 
@@ -339,11 +351,11 @@
     assert false : "Must always be able to find and remove the instantiation";
   }
 
-  private void removeReferencesToThis(IRCode code) {
+  private void removeReferencesToThis(IRCode code, MethodProcessor methodProcessor) {
     fixupStaticizedThisUsers(code, code.getThis());
   }
 
-  private void rewriteReferences(IRCode code) {
+  private void rewriteReferences(IRCode code, MethodProcessor methodProcessor) {
     // Process all singleton field reads and rewrite their users.
     List<StaticGet> singletonFieldReads =
         Streams.stream(code.instructionIterator())
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index 1b34adf..941d7e2 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -35,33 +35,60 @@
 
   public final DexItemFactory factory;
 
+  public final DexType kotlinAssertions;
   public final Functional functional;
   public final Intrinsics intrinsics;
   public final Metadata metadata;
 
+  // Mappings from JVM types to Kotlin types (of type DexType)
   final Map<DexType, DexType> knownTypeConversion;
 
   public Kotlin(DexItemFactory factory) {
     this.factory = factory;
 
+    this.kotlinAssertions = factory.createType(addKotlinPrefix("_Assertions;"));
     this.functional = new Functional();
     this.intrinsics = new Intrinsics();
     this.metadata = new Metadata();
 
+    // See {@link org.jetbrains.kotlin.metadata.jvm.deserialization.ClassMapperLite}
     this.knownTypeConversion =
         ImmutableMap.<DexType, DexType>builder()
             // https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/index.html
+            // Boxed primitives and arrays
             .put(factory.booleanType, factory.createType(addKotlinPrefix("Boolean;")))
+            .put(factory.booleanArrayType, factory.createType(addKotlinPrefix("BooleanArray;")))
             .put(factory.byteType, factory.createType(addKotlinPrefix("Byte;")))
-            .put(factory.charType, factory.createType(addKotlinPrefix("Character;")))
+            .put(factory.byteArrayType, factory.createType(addKotlinPrefix("ByteArray;")))
+            .put(factory.charType, factory.createType(addKotlinPrefix("Char;")))
+            .put(factory.charArrayType, factory.createType(addKotlinPrefix("CharArray;")))
             .put(factory.shortType, factory.createType(addKotlinPrefix("Short;")))
+            .put(factory.shortArrayType, factory.createType(addKotlinPrefix("ShortArray;")))
             .put(factory.intType, factory.createType(addKotlinPrefix("Int;")))
+            .put(factory.intArrayType, factory.createType(addKotlinPrefix("IntArray;")))
             .put(factory.longType, factory.createType(addKotlinPrefix("Long;")))
+            .put(factory.longArrayType, factory.createType(addKotlinPrefix("LongArray;")))
             .put(factory.floatType, factory.createType(addKotlinPrefix("Float;")))
+            .put(factory.floatArrayType, factory.createType(addKotlinPrefix("FloatArray;")))
             .put(factory.doubleType, factory.createType(addKotlinPrefix("Double;")))
+            .put(factory.doubleArrayType, factory.createType(addKotlinPrefix("DoubleArray;")))
+            // Other intrinsics
             .put(factory.voidType, factory.createType(addKotlinPrefix("Unit;")))
+            .put(factory.objectType, factory.createType(addKotlinPrefix("Any;")))
+            .put(factory.boxedVoidType, factory.createType(addKotlinPrefix("Nothing;")))
             .put(factory.stringType, factory.createType(addKotlinPrefix("String;")))
-            // TODO(b/70169921): Collections?
+            .put(factory.charSequenceType, factory.createType(addKotlinPrefix("CharSequence;")))
+            .put(factory.throwableType, factory.createType(addKotlinPrefix("Throwable;")))
+            .put(factory.cloneableType, factory.createType(addKotlinPrefix("Cloneable;")))
+            .put(factory.boxedNumberType, factory.createType(addKotlinPrefix("Number;")))
+            .put(factory.comparableType, factory.createType(addKotlinPrefix("Comparable;")))
+            .put(factory.enumType, factory.createType(addKotlinPrefix("Enum;")))
+            // TODO(b/70169921): (Mutable) Collections?
+            // .../jvm/functions/FunctionN -> .../FunctionN
+            .putAll(
+                IntStream.rangeClosed(0, 22).boxed().collect(Collectors.toMap(
+                    i -> factory.createType(addKotlinPrefix("jvm/functions/Function" + i + ";")),
+                    i -> factory.createType(addKotlinPrefix("Function" + i + ";")))))
             .build();
   }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index 0edb554..48f29c2 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -14,7 +14,9 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.List;
@@ -48,6 +50,16 @@
 
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    if (appView.options().enableKotlinMetadataRewritingForRenamedClasses
+        && lens.lookupType(clazz.type, appView.dexItemFactory()) != clazz.type) {
+      String renamedClassifier = toRenamedClassifier(clazz.type, appView, lens);
+      if (renamedClassifier != null) {
+        assert !kmClass.getName().equals(renamedClassifier);
+        kmClass.setName(renamedClassifier);
+      }
+    }
+
+    // Rewriting upward hierarchy.
     List<KmType> superTypes = kmClass.getSupertypes();
     superTypes.clear();
     for (DexType itfType : clazz.interfaces.values) {
@@ -66,7 +78,29 @@
       superTypes.add(toKmType(addKotlinPrefix("Any;")));
     }
 
-    if (!appView.options().enableKotlinMetadataRewriting) {
+    // Rewriting downward hierarchies: nested.
+    List<String> nestedClasses = kmClass.getNestedClasses();
+    nestedClasses.clear();
+    for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
+      DexString renamedInnerName = lens.lookupInnerName(innerClassAttribute, appView.options());
+      if (renamedInnerName != null) {
+        nestedClasses.add(renamedInnerName.toString());
+      }
+    }
+
+    // Rewriting downward hierarchies: sealed.
+    List<String> sealedSubclasses = kmClass.getSealedSubclasses();
+    sealedSubclasses.clear();
+    if (IS_SEALED.invoke(kmClass.getFlags())) {
+      for (DexType subtype : appView.appInfo().allImmediateSubtypes(clazz.type)) {
+        String classifier = toRenamedClassifier(subtype, appView, lens);
+        if (classifier != null) {
+          sealedSubclasses.add(classifier);
+        }
+      }
+    }
+
+    if (!appView.options().enableKotlinMetadataRewritingForMembers) {
       return;
     }
     List<KmConstructor> constructors = kmClass.getConstructors();
@@ -81,18 +115,9 @@
       }
     }
 
-    rewriteDeclarationContainer(kmClass, appView, lens);
+    // TODO(b/70169921): enum entries
 
-    List<String> sealedSubclasses = kmClass.getSealedSubclasses();
-    sealedSubclasses.clear();
-    if (IS_SEALED.invoke(kmClass.getFlags())) {
-      for (DexType subtype : appView.appInfo().allImmediateSubtypes(clazz.type)) {
-        String classifier = toRenamedClassifier(subtype, appView, lens);
-        if (classifier != null) {
-          sealedSubclasses.add(classifier);
-        }
-      }
-    }
+    rewriteDeclarationContainer(kmClass, appView, lens);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
index f8c1c12..07ec533 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -36,6 +36,7 @@
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
     // TODO(b/70169921): no idea yet!
     assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
+            || appView.options().enableKotlinMetadataRewritingForRenamedClasses
         : toString();
   }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
index 94a993b..58c5aff 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -37,7 +37,7 @@
 
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    if (!appView.options().enableKotlinMetadataRewriting) {
+    if (!appView.options().enableKotlinMetadataRewritingForMembers) {
       return;
     }
     rewriteDeclarationContainer(kmPackage, appView, lens);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
index 9eb70f8..5eac335 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -37,7 +37,7 @@
 
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    if (!appView.options().enableKotlinMetadataRewriting) {
+    if (!appView.options().enableKotlinMetadataRewritingForMembers) {
       return;
     }
     rewriteDeclarationContainer(kmPackage, appView, lens);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
index 5382e44..62b5355 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.KmFunctionProcessor;
 import com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.KmPropertyProcessor;
+import com.android.tools.r8.utils.Reporter;
 import java.util.HashMap;
 import java.util.Map;
 import kotlinx.metadata.KmDeclarationContainer;
@@ -88,36 +89,37 @@
     }
   }
 
-  public static void markKotlinMemberInfo(DexClass clazz, KotlinInfo kotlinInfo) {
+  public static void markKotlinMemberInfo(
+      DexClass clazz, KotlinInfo kotlinInfo, Reporter reporter) {
     if (kotlinInfo == null || !kotlinInfo.hasDeclarations()) {
       return;
     }
     if (kotlinInfo.isClass()) {
-      markKotlinMemberInfo(clazz, kotlinInfo.asClass().kmClass);
+      markKotlinMemberInfo(clazz, kotlinInfo.asClass().kmClass, reporter);
     } else if (kotlinInfo.isFile()) {
-      markKotlinMemberInfo(clazz, kotlinInfo.asFile().kmPackage);
+      markKotlinMemberInfo(clazz, kotlinInfo.asFile().kmPackage, reporter);
     } else if (kotlinInfo.isClassPart()) {
-      markKotlinMemberInfo(clazz, kotlinInfo.asClassPart().kmPackage);
+      markKotlinMemberInfo(clazz, kotlinInfo.asClassPart().kmPackage, reporter);
     } else {
       throw new Unreachable("Unexpected KotlinInfo: " + kotlinInfo);
     }
   }
 
   private static void markKotlinMemberInfo(
-      DexClass clazz, KmDeclarationContainer kmDeclarationContainer) {
+      DexClass clazz, KmDeclarationContainer kmDeclarationContainer, Reporter reporter) {
     Map<String, KmFunction> kmFunctionMap = new HashMap<>();
     Map<String, KmProperty> kmPropertyFieldMap = new HashMap<>();
     Map<String, KmProperty> kmPropertyGetterMap = new HashMap<>();
     Map<String, KmProperty> kmPropertySetterMap = new HashMap<>();
 
     kmDeclarationContainer.getFunctions().forEach(kmFunction -> {
-      KmFunctionProcessor functionProcessor = new KmFunctionProcessor(kmFunction);
+      KmFunctionProcessor functionProcessor = new KmFunctionProcessor(kmFunction, reporter);
       if (functionProcessor.signature() != null) {
         kmFunctionMap.put(functionProcessor.signature().asString(), kmFunction);
       }
     });
     kmDeclarationContainer.getProperties().forEach(kmProperty -> {
-      KmPropertyProcessor propertyProcessor = new KmPropertyProcessor(kmProperty);
+      KmPropertyProcessor propertyProcessor = new KmPropertyProcessor(kmProperty, reporter);
       if (propertyProcessor.fieldSignature() != null) {
         kmPropertyFieldMap.put(propertyProcessor.fieldSignature().asString(), kmProperty);
       }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
index bdd7f60..fadf9cb 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
@@ -3,9 +3,22 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix;
+
+import com.android.tools.r8.errors.InvalidDescriptorException;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 import kotlinx.metadata.KmConstructor;
 import kotlinx.metadata.KmConstructorExtensionVisitor;
 import kotlinx.metadata.KmConstructorVisitor;
@@ -24,6 +37,94 @@
 
 class KotlinMetadataJvmExtensionUtils {
 
+  // Mappings from Kotlin types to JVM types (of String)
+  private static final Map<String, String> knownTypeConversion =
+      // See {@link org.jetbrains.kotlin.metadata.jvm.deserialization.ClassMapperLite}
+      ImmutableMap.<String, String>builder()
+          // Boxed primitives and arrays
+          .put(addKotlinPrefix("Boolean;"), "Z")
+          .put(addKotlinPrefix("BooleanArray;"), "[Z")
+          .put(addKotlinPrefix("Byte;"), "B")
+          .put(addKotlinPrefix("ByteArray;"), "[B")
+          .put(addKotlinPrefix("Char;"), "C")
+          .put(addKotlinPrefix("CharArray;"), "[C")
+          .put(addKotlinPrefix("Short;"), "S")
+          .put(addKotlinPrefix("ShortArray;"), "[S")
+          .put(addKotlinPrefix("Int;"), "I")
+          .put(addKotlinPrefix("IntArray;"), "[I")
+          .put(addKotlinPrefix("Long;"), "J")
+          .put(addKotlinPrefix("LongArray;"), "[J")
+          .put(addKotlinPrefix("Float;"), "F")
+          .put(addKotlinPrefix("FloatArray;"), "[F")
+          .put(addKotlinPrefix("Double;"), "D")
+          .put(addKotlinPrefix("DoubleArray;"), "[D")
+          // Other intrinsics
+          .put(addKotlinPrefix("Unit;"), "V")
+          .put(addKotlinPrefix("Any;"), "Ljava/lang/Object;")
+          .put(addKotlinPrefix("Nothing;"), "Ljava/lang/Void;")
+          .putAll(ImmutableList.of(
+              "String", "CharSequence", "Throwable", "Cloneable", "Number", "Comparable", "Enum")
+                  .stream().collect(Collectors.toMap(
+                      t -> addKotlinPrefix(t + ";"),
+                      t -> "Ljava/lang/" + t + ";")))
+          // Collections
+          .putAll(ImmutableList.of("Iterator", "Collection", "List", "Set", "Map", "ListIterator")
+              .stream().collect(Collectors.toMap(
+                  t -> addKotlinPrefix("collections/" + t + ";"),
+                  t -> "Ljava/util/" + t + ";")))
+          .putAll(ImmutableList.of("Iterator", "Collection", "List", "Set", "Map", "ListIterator")
+              .stream().collect(Collectors.toMap(
+                  t -> addKotlinPrefix("collections/Mutable" + t + ";"),
+                  t -> "Ljava/util/" + t + ";")))
+          .put(addKotlinPrefix("collections/Iterable;"), "Ljava/lang/Iterable;")
+          .put(addKotlinPrefix("collections/MutableIterable;"), "Ljava/lang/Iterable;")
+          .put(addKotlinPrefix("collections/Map.Entry;"), "Ljava/util/Map$Entry;")
+          .put(addKotlinPrefix("collections/MutableMap.MutableEntry;"), "Ljava/util/Map$Entry;")
+          // .../FunctionN -> .../jvm/functions/FunctionN
+          .putAll(
+              IntStream.rangeClosed(0, 22).boxed().collect(Collectors.toMap(
+                  i -> addKotlinPrefix("Function" + i + ";"),
+                  i -> addKotlinPrefix("jvm/functions/Function" + i + ";"))))
+          .build();
+
+  private static String remapKotlinType(String type) {
+    if (knownTypeConversion.containsKey(type)) {
+      return knownTypeConversion.get(type);
+    }
+    return type;
+  }
+
+  // Kotlin @Metadata deserialization has plain "kotlin", which will be relocated in r8lib.
+  // See b/70169921#comment57 for more details.
+  // E.g., desc: (Labc/xyz/C;Lkotlin/Function1;)kotlin/Unit
+  // remapped desc would be: (Labc/xyz/C;Lkotlin/jvm/functions/Function1;)V
+  private static String remapKotlinTypeInDesc(String desc, Reporter reporter) {
+    if (desc == null) {
+      return null;
+    }
+    if (desc.isEmpty()) {
+      return desc;
+    }
+    String[] parameterTypes;
+    try {
+      parameterTypes = DescriptorUtils.getArgumentTypeDescriptors(desc);
+      for (int i = 0; i < parameterTypes.length; i++) {
+        parameterTypes[i] = remapKotlinType(parameterTypes[i]);
+      }
+    } catch (InvalidDescriptorException e) {
+      // JvmMethodSignature from @Metadata is not 100% reliable (due to its own optimization using
+      // map, relocation in r8lib, etc.)
+      reporter.info(
+          new StringDiagnostic(
+              "Invalid descriptor (deserialized from Kotlin @Metadata): " + desc));
+      return desc;
+    }
+    int index = desc.indexOf(')');
+    assert 0 < index && index < desc.length() : desc;
+    String returnType = remapKotlinType(desc.substring(index + 1));
+    return "(" + StringUtils.join(Arrays.asList(parameterTypes), "") + ")" + returnType;
+  }
+
   static JvmFieldSignature toJvmFieldSignature(DexField field) {
     return new JvmFieldSignature(field.name.toString(), field.type.toDescriptorString());
   }
@@ -42,7 +143,7 @@
   static class KmConstructorProcessor {
     private JvmMethodSignature signature = null;
 
-    KmConstructorProcessor(KmConstructor kmConstructor) {
+    KmConstructorProcessor(KmConstructor kmConstructor, Reporter reporter) {
       kmConstructor.accept(new KmConstructorVisitor() {
         @Override
         public KmConstructorExtensionVisitor visitExtensions(KmExtensionType type) {
@@ -58,6 +159,12 @@
           };
         }
       });
+      if (signature != null) {
+        String remappedDesc = remapKotlinTypeInDesc(signature.getDesc(), reporter);
+        if (remappedDesc != null && !remappedDesc.equals(signature.getDesc())) {
+          signature = new JvmMethodSignature(signature.getName(), remappedDesc);
+        }
+      }
     }
 
     JvmMethodSignature signature() {
@@ -69,7 +176,7 @@
     // Custom name via @JvmName("..."). Otherwise, null.
     private JvmMethodSignature signature = null;
 
-    KmFunctionProcessor(KmFunction kmFunction) {
+    KmFunctionProcessor(KmFunction kmFunction, Reporter reporter) {
       kmFunction.accept(new KmFunctionVisitor() {
         @Override
         public KmFunctionExtensionVisitor visitExtensions(KmExtensionType type) {
@@ -85,6 +192,12 @@
           };
         }
       });
+      if (signature != null) {
+        String remappedDesc = remapKotlinTypeInDesc(signature.getDesc(), reporter);
+        if (remappedDesc != null && !remappedDesc.equals(signature.getDesc())) {
+          signature = new JvmMethodSignature(signature.getName(), remappedDesc);
+        }
+      }
     }
 
     JvmMethodSignature signature() {
@@ -99,7 +212,7 @@
     // Custom getter via @set:JvmName("..."). Otherwise, null.
     private JvmMethodSignature setterSignature = null;
 
-    KmPropertyProcessor(KmProperty kmProperty) {
+    KmPropertyProcessor(KmProperty kmProperty, Reporter reporter) {
       kmProperty.accept(new KmPropertyVisitor() {
         @Override
         public KmPropertyExtensionVisitor visitExtensions(KmExtensionType type) {
@@ -123,6 +236,24 @@
           };
         }
       });
+      if (fieldSignature != null) {
+        String remappedDesc = remapKotlinType(fieldSignature.getDesc());
+        if (remappedDesc != null && !remappedDesc.equals(fieldSignature.getDesc())) {
+          fieldSignature = new JvmFieldSignature(fieldSignature.getName(), remappedDesc);
+        }
+      }
+      if (getterSignature != null) {
+        String remappedDesc = remapKotlinTypeInDesc(getterSignature.getDesc(), reporter);
+        if (remappedDesc != null && !remappedDesc.equals(getterSignature.getDesc())) {
+          getterSignature = new JvmMethodSignature(getterSignature.getName(), remappedDesc);
+        }
+      }
+      if (setterSignature != null) {
+        String remappedDesc = remapKotlinTypeInDesc(setterSignature.getDesc(), reporter);
+        if (remappedDesc != null && !remappedDesc.equals(setterSignature.getDesc())) {
+          setterSignature = new JvmMethodSignature(setterSignature.getName(), remappedDesc);
+        }
+      }
     }
 
     JvmFieldSignature fieldSignature() {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
index 5658b0d..b295da3 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -68,7 +68,10 @@
           if (kotlinInfo != null) {
             // If @Metadata is still associated, this class should not be renamed
             // (by {@link ClassNameMinifier} of course).
+            // Or, we start maintaining @Metadata for renamed classes.
+            // TODO(b/70169921): if this option is settled down, this assertion is meaningless.
             assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
+                    || appView.options().enableKotlinMetadataRewritingForRenamedClasses
                 : clazz.toSourceString() + " != "
                     + lens.lookupType(clazz.type, appView.dexItemFactory());
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
index 8a8773a..94c65e3 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -46,16 +46,16 @@
 
   static String toRenamedClassifier(
       DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    // E.g., [Ljava/lang/String; -> kotlin/Array
-    if (type.isArrayType()) {
-      return NAME + "/Array";
-    }
-    // E.g., void -> kotlin/Unit
+    // E.g., V -> kotlin/Unit, J -> kotlin/Long, [J -> kotlin/LongArray
     if (appView.dexItemFactory().kotlin.knownTypeConversion.containsKey(type)) {
       DexType convertedType = appView.dexItemFactory().kotlin.knownTypeConversion.get(type);
       assert convertedType != null;
       return descriptorToKotlinClassifier(convertedType.toDescriptorString());
     }
+    // E.g., [Ljava/lang/String; -> kotlin/Array
+    if (type.isArrayType()) {
+      return NAME + "/Array";
+    }
     // For library or classpath class, synthesize @Metadata always.
     // For a program class, make sure it is live.
     if (!appView.appInfo().isNonProgramTypeOrLiveProgramType(type)) {
@@ -79,6 +79,9 @@
     //   and/or why wiping out flags works for KmType but not KmFunction?!
     KmType kmType = new KmType(flagsOf());
     kmType.visitClass(classifier);
+    // TODO(b/70169921): Need to set arguments as type parameter.
+    //  E.g., for kotlin/Function1<P1, R>, P1 and R are recorded inside KmType.arguments, which
+    //  enables kotlinc to resolve `this` type and return type of the lambda.
     return kmType;
   }
 
@@ -128,7 +131,7 @@
     }
     DexMethod renamedMethod = lens.lookupMethod(method.method, appView.dexItemFactory());
     // For a library method override, we should not have renamed it.
-    assert !method.isLibraryMethodOverride().isTrue() || renamedMethod == method.method
+    assert !method.isLibraryMethodOverride().isTrue() || renamedMethod.name == method.method.name
         : method.toSourceString() + " -> " + renamedMethod.toSourceString();
     // TODO(b/70169921): Should we keep kotlin-specific flags only while synthesizing the base
     //  value from general JVM flags?
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
index 59f7dce..28a6eb1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
@@ -53,6 +53,7 @@
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
     // TODO(b/70169921): no idea yet!
     assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
+            || appView.options().enableKotlinMetadataRewritingForRenamedClasses
         : toString();
   }
 
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index d13cc3b..1c75720 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -115,7 +115,9 @@
       if (!renaming.containsKey(clazz.type)) {
         DexString renamed = computeName(clazz.type);
         renaming.put(clazz.type, renamed);
-        KotlinMetadataRewriter.removeKotlinMetadataFromRenamedClass(appView, clazz);
+        if (!appView.options().enableKotlinMetadataRewritingForRenamedClasses) {
+          KotlinMetadataRewriter.removeKotlinMetadataFromRenamedClass(appView, clazz);
+        }
         // If the class is a member class and it has used $ separator, its renamed name should have
         // the same separator (as long as inner-class attribute is honored).
         assert !keepInnerClassStructure
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
index 8896b56..b178579 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -84,34 +84,52 @@
 
   private void reserveFieldNames() {
     // Reserve all field names that need to be reserved.
-    for (DexClass clazz : appView.appInfo().app().asDirect().allClasses()) {
-      ReservedFieldNamingState reservedNames = null;
-      for (DexEncodedField field : clazz.fields()) {
-        DexString reservedName = strategy.getReservedName(field, clazz);
-        if (reservedName != null) {
-          if (reservedNames == null) {
-            reservedNames = getOrCreateReservedFieldNamingState(clazz.type);
-          }
-          reservedNames.markReservedDirectly(reservedName, field.field.name, field.field.type);
-          if (reservedName != field.field.name) {
-            renaming.put(field.field, reservedName);
-          }
-        }
-      }
+    appView
+        .appInfo()
+        .forEachTypeInHierarchyOfLiveProgramClasses(
+            clazz -> {
+              ReservedFieldNamingState reservedNames = null;
+              for (DexEncodedField field : clazz.fields()) {
+                DexString reservedName = strategy.getReservedName(field, clazz);
+                if (reservedName != null) {
+                  if (reservedNames == null) {
+                    reservedNames = getOrCreateReservedFieldNamingState(clazz.type);
+                  }
+                  reservedNames.markReservedDirectly(
+                      reservedName, field.field.name, field.field.type);
+                  // TODO(b/148846065): Consider lazily computing the renaming on actual lookups.
+                  if (reservedName != field.field.name) {
+                    renaming.put(field.field, reservedName);
+                  }
+                }
+              }
 
-      // For interfaces, propagate reserved names to all implementing classes.
-      if (clazz.isInterface() && reservedNames != null) {
-        for (DexType implementationType :
-            appView.appInfo().allImmediateImplementsSubtypes(clazz.type)) {
-          DexClass implementation = appView.definitionFor(implementationType);
-          if (implementation != null) {
-            assert !implementation.isInterface();
-            getOrCreateReservedFieldNamingState(implementationType)
-                .includeReservations(reservedNames);
-          }
-        }
-      }
-    }
+              // For interfaces, propagate reserved names to all implementing classes.
+              if (clazz.isInterface() && reservedNames != null) {
+                for (DexType implementationType :
+                    appView.appInfo().allImmediateImplementsSubtypes(clazz.type)) {
+                  DexClass implementation = appView.definitionFor(implementationType);
+                  if (implementation != null) {
+                    assert !implementation.isInterface();
+                    getOrCreateReservedFieldNamingState(implementationType)
+                        .includeReservations(reservedNames);
+                  }
+                }
+              }
+            });
+
+    // TODO(b/148846065): Consider lazily computing the renaming on actual lookups.
+    appView
+        .appInfo()
+        .forEachReferencedClasspathClass(
+            clazz -> {
+              for (DexEncodedField field : clazz.fields()) {
+                DexString reservedName = strategy.getReservedName(field, clazz);
+                if (reservedName != null && reservedName != field.field.name) {
+                  renaming.put(field.field, reservedName);
+                }
+              }
+            });
 
     propagateReservedFieldNamesUpwards();
   }
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
index 00b5315..bbefa18 100644
--- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -454,7 +454,7 @@
           for (DexEncodedMethod method : implementedMethods) {
             Wrapper<DexMethod> wrapped = equivalence.wrap(method.method);
             InterfaceMethodGroupState groupState = globalStateMap.get(wrapped);
-            assert groupState != null;
+            assert groupState != null : wrapped;
             groupState.addCallSite(callSite);
             callSiteMethods.add(wrapped);
           }
@@ -636,19 +636,23 @@
   }
 
   private void computeReservationFrontiersForAllImplementingClasses() {
-    for (DexClass clazz : appView.appInfo().app().asDirect().allClasses()) {
-      // TODO(b/133091438): Extend the if check to test for !clazz.isLibrary().
-      if (!clazz.isInterface()) {
-        for (DexType directlyImplemented : appView.appInfo().implementedInterfaces(clazz.type)) {
-          InterfaceReservationState iState = interfaceStateMap.get(directlyImplemented);
-          if (iState != null) {
-            DexType frontierType = minifierState.getFrontier(clazz.type);
-            assert minifierState.getReservationState(frontierType) != null;
-            iState.reservationTypes.add(frontierType);
-          }
-        }
-      }
-    }
+    appView
+        .appInfo()
+        .forEachTypeInHierarchyOfLiveProgramClasses(
+            clazz -> {
+              // TODO(b/133091438): Extend the if check to test for !clazz.isLibrary().
+              if (!clazz.isInterface()) {
+                for (DexType directlyImplemented :
+                    appView.appInfo().implementedInterfaces(clazz.type)) {
+                  InterfaceReservationState iState = interfaceStateMap.get(directlyImplemented);
+                  if (iState != null) {
+                    DexType frontierType = minifierState.getFrontier(clazz.type);
+                    assert minifierState.getReservationState(frontierType) != null;
+                    iState.reservationTypes.add(frontierType);
+                  }
+                }
+              }
+            });
   }
 
   private boolean verifyAllCallSitesAreRepresentedIn(List<Wrapper<DexMethod>> groups) {
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index d506d84..2e68b4f 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
@@ -276,14 +275,6 @@
         DexString reservedName = strategy.getReservedName(method, holder);
         if (reservedName != null) {
           state.reserveName(reservedName, method.method);
-          // This is reserving names which after prefix rewriting will actually override a library
-          // method.
-          if (appView.rewritePrefix.hasRewrittenTypeInSignature(method.method.proto, appView)) {
-            state.reserveName(
-                reservedName,
-                DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
-                    method.method, method.method.holder, appView));
-          }
         }
       }
     }
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 e768589..359de49 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -213,11 +213,13 @@
 
     final AppView<?> appView;
     private final DexItemFactory factory;
+    private final boolean desugaredLibraryRenaming;
 
     public MinifierMemberNamingStrategy(AppView<?> appView) {
       super(appView.options().getProguardConfiguration().getObfuscationDictionary(), false);
       this.appView = appView;
       this.factory = appView.dexItemFactory();
+      this.desugaredLibraryRenaming = appView.rewritePrefix.isRewriting();
     }
 
     @Override
@@ -260,6 +262,12 @@
           || appView.rootSet().mayNotBeMinified(method.method, appView)) {
         return method.method.name;
       }
+      if (desugaredLibraryRenaming
+          && method.isLibraryMethodOverride().isTrue()
+          && appView.rewritePrefix.hasRewrittenTypeInSignature(method.method.proto, appView)) {
+        // With desugared library, call-backs names are reserved here.
+        return method.method.name;
+      }
       return null;
     }
 
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 814ef4c..7b6d191 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -44,6 +44,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.BiPredicate;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 /**
@@ -90,15 +91,19 @@
     timing.begin("MappingInterfaces");
     Set<DexClass> interfaces = new TreeSet<>((a, b) -> a.type.slowCompareTo(b.type));
     AppInfoWithLiveness appInfo = appView.appInfo();
-    for (DexClass dexClass : appInfo.app().asDirect().allClasses()) {
-      if (dexClass.isInterface()) {
-        // Only visit top level interfaces because computeMapping will visit the hierarchy.
-        if (dexClass.interfaces.isEmpty()) {
-          computeMapping(dexClass.type, nonPrivateMembers);
-        }
-        interfaces.add(dexClass);
-      }
-    }
+    Consumer<DexClass> consumer =
+        dexClass -> {
+          if (dexClass.isInterface()) {
+            // Only visit top level interfaces because computeMapping will visit the hierarchy.
+            if (dexClass.interfaces.isEmpty()) {
+              computeMapping(dexClass.type, nonPrivateMembers);
+            }
+            interfaces.add(dexClass);
+          }
+        };
+    // For union-find of interface methods we also need to add the library types above live types.
+    appInfo.forEachTypeInHierarchyOfLiveProgramClasses(consumer);
+    appInfo.forEachReferencedClasspathClass(consumer::accept);
     assert nonPrivateMembers.isEmpty();
     timing.end();
 
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
index 8d3e956..89e7c9d 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
@@ -262,8 +262,10 @@
    * <ul>
    *   <li>at dalvik.system.NativeStart.main(NativeStart.java:99)
    *   <li>at dalvik.system.NativeStart.main(:99)
-   *   <li>dalvik.system.NativeStart.main(Foo.java:)
+   *   <li>at dalvik.system.NativeStart.main(Foo.java:)
    *   <li>at dalvik.system.NativeStart.main(Native Method)
+   *   <li>at classloader/named_module@version/foo.bar.baz(:20)
+   *   <li>at classloader//foo.bar.baz(:20)
    * </ul>
    *
    * <p>Empirical evidence suggests that the "at" string is never localized.
@@ -275,6 +277,8 @@
 
     private final String startingWhitespace;
     private final String at;
+    private final String classLoaderName;
+    private final String moduleName;
     private final String clazz;
     private final String method;
     private final String methodAsString;
@@ -285,6 +289,8 @@
     private AtLine(
         String startingWhitespace,
         String at,
+        String classLoaderName,
+        String moduleName,
         String clazz,
         String method,
         String methodAsString,
@@ -293,6 +299,8 @@
         boolean isAmbiguous) {
       this.startingWhitespace = startingWhitespace;
       this.at = at;
+      this.classLoaderName = classLoaderName;
+      this.moduleName = moduleName;
       this.clazz = clazz;
       this.method = method;
       this.methodAsString = methodAsString;
@@ -314,11 +322,13 @@
           || line.charAt(firstNonWhiteSpace + 2) != ' ') {
         return null;
       }
-      int classStartIndex = firstNonWhiteSpaceCharacterFromIndex(line, firstNonWhiteSpace + 2);
-      if (classStartIndex >= line.length() || classStartIndex != firstNonWhiteSpace + 3) {
+      int classClassLoaderOrModuleStartIndex =
+          firstNonWhiteSpaceCharacterFromIndex(line, firstNonWhiteSpace + 2);
+      if (classClassLoaderOrModuleStartIndex >= line.length()
+          || classClassLoaderOrModuleStartIndex != firstNonWhiteSpace + 3) {
         return null;
       }
-      int parensStart = firstCharFromIndex(line, classStartIndex, '(');
+      int parensStart = firstCharFromIndex(line, classClassLoaderOrModuleStartIndex, '(');
       if (parensStart >= line.length()) {
         return null;
       }
@@ -330,7 +340,7 @@
         return null;
       }
       int methodSeparator = line.lastIndexOf('.', parensStart);
-      if (methodSeparator <= classStartIndex) {
+      if (methodSeparator <= classClassLoaderOrModuleStartIndex) {
         return null;
       }
       // Check if we have a filename and position.
@@ -348,11 +358,32 @@
       } else {
         fileName = line.substring(parensStart + 1, parensEnd);
       }
+      String classLoaderName = null;
+      String moduleName = null;
+      int classStartIndex = classClassLoaderOrModuleStartIndex;
+      int classLoaderOrModuleEndIndex =
+          firstCharFromIndex(line, classClassLoaderOrModuleStartIndex, '/');
+      if (classLoaderOrModuleEndIndex < methodSeparator) {
+        int moduleEndIndex = firstCharFromIndex(line, classLoaderOrModuleEndIndex + 1, '/');
+        if (moduleEndIndex < methodSeparator) {
+          // The stack trace contains both a class loader and module
+          classLoaderName =
+              line.substring(classClassLoaderOrModuleStartIndex, classLoaderOrModuleEndIndex);
+          moduleName = line.substring(classLoaderOrModuleEndIndex + 1, moduleEndIndex);
+          classStartIndex = moduleEndIndex + 1;
+        } else {
+          moduleName =
+              line.substring(classClassLoaderOrModuleStartIndex, classLoaderOrModuleEndIndex);
+          classStartIndex = classLoaderOrModuleEndIndex + 1;
+        }
+      }
       String className = line.substring(classStartIndex, methodSeparator);
       String methodName = line.substring(methodSeparator + 1, parensStart);
       return new AtLine(
           line.substring(0, firstNonWhiteSpace),
-          line.substring(firstNonWhiteSpace, classStartIndex),
+          line.substring(firstNonWhiteSpace, classClassLoaderOrModuleStartIndex),
+          classLoaderName,
+          moduleName,
           className,
           methodName,
           className + "." + methodName,
@@ -368,6 +399,27 @@
     @Override
     List<StackTraceLine> retrace(RetraceBase retraceBase, boolean verbose) {
       List<StackTraceLine> lines = new ArrayList<>();
+      String retraceClassLoaderName = classLoaderName;
+      if (retraceClassLoaderName != null) {
+        ClassReference classLoaderReference = Reference.classFromTypeName(retraceClassLoaderName);
+        retraceBase
+            .retrace(classLoaderReference)
+            .forEach(
+                classElement -> {
+                  retraceClassAndMethods(
+                      retraceBase, verbose, lines, classElement.getClassReference().getTypeName());
+                });
+      } else {
+        retraceClassAndMethods(retraceBase, verbose, lines, retraceClassLoaderName);
+      }
+      return lines;
+    }
+
+    private void retraceClassAndMethods(
+        RetraceBase retraceBase,
+        boolean verbose,
+        List<StackTraceLine> lines,
+        String classLoaderName) {
       ClassReference classReference = Reference.classFromTypeName(clazz);
       retraceBase
           .retrace(classReference)
@@ -380,6 +432,8 @@
                     new AtLine(
                         startingWhitespace,
                         at,
+                        classLoaderName,
+                        moduleName,
                         methodReference.getHolderClass().getTypeName(),
                         methodReference.getMethodName(),
                         methodDescriptionFromMethodReference(methodReference, verbose),
@@ -390,13 +444,20 @@
                             : linePosition,
                         methodElement.getRetraceMethodResult().isAmbiguous()));
               });
-      return lines;
     }
 
     @Override
     public String toString() {
       StringBuilder sb = new StringBuilder(startingWhitespace);
       sb.append(at);
+      if (classLoaderName != null) {
+        sb.append(classLoaderName);
+        sb.append("/");
+      }
+      if (moduleName != null) {
+        sb.append(moduleName);
+        sb.append("/");
+      }
       sb.append(methodAsString);
       sb.append("(");
       sb.append(fileName);
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index a2a3380..dcc6877 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -6,10 +6,11 @@
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.graph.GraphLense.rewriteReferenceKeys;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -24,11 +25,14 @@
 import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
 import com.android.tools.r8.graph.FieldAccessInfoImpl;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.graph.PresortedComparable;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.utils.CollectionUtils;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.PredicateSet;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.ImmutableList;
@@ -44,14 +48,15 @@
 import java.util.Collections;
 import java.util.Deque;
 import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.SortedSet;
 import java.util.TreeMap;
+import java.util.function.Consumer;
 import java.util.function.Function;
-import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 /** Encapsulates liveness and reachability information for an application. */
@@ -184,7 +189,7 @@
 
   // TODO(zerny): Clean up the constructors so we have just one.
   AppInfoWithLiveness(
-      DexApplication application,
+      DirectMappedDexApplication application,
       Set<DexType> liveTypes,
       Set<DexType> instantiatedAnnotationTypes,
       Set<DexType> instantiatedAppServices,
@@ -396,7 +401,7 @@
 
   private AppInfoWithLiveness(
       AppInfoWithLiveness previous,
-      DexApplication application,
+      DirectMappedDexApplication application,
       Collection<DexType> removedClasses,
       Collection<DexReference> additionalPinnedItems) {
     this(
@@ -448,90 +453,6 @@
     assert removedClasses == null || assertNoItemRemoved(previous.pinnedItems, removedClasses);
   }
 
-  private AppInfoWithLiveness(
-      AppInfoWithLiveness previous, DirectMappedDexApplication application, GraphLense lense) {
-    super(application);
-    this.liveTypes = rewriteItems(previous.liveTypes, lense::lookupType);
-    this.instantiatedAnnotationTypes =
-        rewriteItems(previous.instantiatedAnnotationTypes, lense::lookupType);
-    this.instantiatedAppServices =
-        rewriteItems(previous.instantiatedAppServices, lense::lookupType);
-    this.instantiatedTypes = rewriteItems(previous.instantiatedTypes, lense::lookupType);
-    this.instantiatedLambdas = rewriteItems(previous.instantiatedLambdas, lense::lookupType);
-    this.targetedMethods = lense.rewriteMethodsConservatively(previous.targetedMethods);
-    this.failedResolutionTargets =
-        lense.rewriteMethodsConservatively(previous.failedResolutionTargets);
-    this.bootstrapMethods = lense.rewriteMethodsConservatively(previous.bootstrapMethods);
-    this.methodsTargetedByInvokeDynamic =
-        lense.rewriteMethodsConservatively(previous.methodsTargetedByInvokeDynamic);
-    this.virtualMethodsTargetedByInvokeDirect =
-        lense.rewriteMethodsConservatively(previous.virtualMethodsTargetedByInvokeDirect);
-    this.liveMethods = lense.rewriteMethodsConservatively(previous.liveMethods);
-    this.fieldAccessInfoCollection =
-        previous.fieldAccessInfoCollection.rewrittenWithLens(application, lense);
-    this.pinnedItems = lense.rewriteReferencesConservatively(previous.pinnedItems);
-    this.virtualInvokes =
-        rewriteKeysConservativelyWhileMergingValues(
-            previous.virtualInvokes, lense::lookupMethodInAllContexts);
-    this.interfaceInvokes =
-        rewriteKeysConservativelyWhileMergingValues(
-            previous.interfaceInvokes, lense::lookupMethodInAllContexts);
-    this.superInvokes =
-        rewriteKeysConservativelyWhileMergingValues(
-            previous.superInvokes, lense::lookupMethodInAllContexts);
-    this.directInvokes =
-        rewriteKeysConservativelyWhileMergingValues(
-            previous.directInvokes, lense::lookupMethodInAllContexts);
-    this.staticInvokes =
-        rewriteKeysConservativelyWhileMergingValues(
-            previous.staticInvokes, lense::lookupMethodInAllContexts);
-    // TODO(sgjesse): Rewrite call sites as well? Right now they are only used by minification
-    // after second tree shaking.
-    this.callSites = previous.callSites;
-    // Don't rewrite pruned types - the removed types are identified by their original name.
-    this.prunedTypes = previous.prunedTypes;
-    this.mayHaveSideEffects =
-        rewriteReferenceKeys(previous.mayHaveSideEffects, lense::lookupReference);
-    this.noSideEffects = rewriteReferenceKeys(previous.noSideEffects, lense::lookupReference);
-    this.assumedValues = rewriteReferenceKeys(previous.assumedValues, lense::lookupReference);
-    assert lense.assertDefinitionsNotModified(
-        previous.alwaysInline.stream()
-            .map(this::definitionFor)
-            .filter(Objects::nonNull)
-            .collect(Collectors.toList()));
-    this.alwaysInline = lense.rewriteMethodsWithRenamedSignature(previous.alwaysInline);
-    this.forceInline = lense.rewriteMethodsWithRenamedSignature(previous.forceInline);
-    this.neverInline = lense.rewriteMethodsWithRenamedSignature(previous.neverInline);
-    this.whyAreYouNotInlining =
-        lense.rewriteMethodsWithRenamedSignature(previous.whyAreYouNotInlining);
-    this.keepConstantArguments =
-        lense.rewriteMethodsWithRenamedSignature(previous.keepConstantArguments);
-    this.keepUnusedArguments =
-        lense.rewriteMethodsWithRenamedSignature(previous.keepUnusedArguments);
-    this.reprocess = lense.rewriteMethodsWithRenamedSignature(previous.reprocess);
-    this.neverReprocess = lense.rewriteMethodsWithRenamedSignature(previous.neverReprocess);
-    assert lense.assertDefinitionsNotModified(
-        previous.neverMerge.stream()
-            .map(this::definitionFor)
-            .filter(Objects::nonNull)
-            .collect(Collectors.toList()));
-    this.alwaysClassInline = previous.alwaysClassInline.rewriteItems(lense::lookupType);
-    this.neverClassInline = rewriteItems(previous.neverClassInline, lense::lookupType);
-    this.neverMerge = rewriteItems(previous.neverMerge, lense::lookupType);
-    this.neverPropagateValue = lense.rewriteReferencesConservatively(previous.neverPropagateValue);
-    this.identifierNameStrings =
-        lense.rewriteReferencesConservatively(previous.identifierNameStrings);
-    // Switchmap classes should never be affected by renaming.
-    assert lense.assertDefinitionsNotModified(
-        previous.switchMaps.keySet().stream()
-            .map(this::definitionFor)
-            .filter(Objects::nonNull)
-            .collect(Collectors.toList()));
-    this.switchMaps = rewriteReferenceKeys(previous.switchMaps, lense::lookupField);
-    this.enumValueInfoMaps = rewriteReferenceKeys(previous.enumValueInfoMaps, lense::lookupType);
-    this.constClassReferences = previous.constClassReferences;
-  }
-
   public AppInfoWithLiveness(
       AppInfoWithLiveness previous,
       Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
@@ -632,6 +553,99 @@
   }
 
   /**
+   * Resolve the methods implemented by the lambda expression that created the {@code callSite}.
+   *
+   * <p>If {@code callSite} was not created as a result of a lambda expression (i.e. the metafactory
+   * is not {@code LambdaMetafactory}), the empty set is returned.
+   *
+   * <p>If the metafactory is neither {@code LambdaMetafactory} nor {@code StringConcatFactory}, a
+   * warning is issued.
+   *
+   * <p>The returned set of methods all have {@code callSite.methodName} as the method name.
+   *
+   * @param callSite Call site to resolve.
+   * @return Methods implemented by the lambda expression that created the {@code callSite}.
+   */
+  public Set<DexEncodedMethod> lookupLambdaImplementedMethods(DexCallSite callSite) {
+    assert checkIfObsolete();
+    List<DexType> callSiteInterfaces = LambdaDescriptor.getInterfaces(callSite, this);
+    if (callSiteInterfaces == null || callSiteInterfaces.isEmpty()) {
+      return Collections.emptySet();
+    }
+    Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
+    Deque<DexType> worklist = new ArrayDeque<>(callSiteInterfaces);
+    Set<DexType> visited = Sets.newIdentityHashSet();
+    while (!worklist.isEmpty()) {
+      DexType iface = worklist.removeFirst();
+      if (!visited.add(iface)) {
+        // Already visited previously. May happen due to "diamond shapes" in the interface
+        // hierarchy.
+        continue;
+      }
+      DexClass clazz = definitionFor(iface);
+      if (clazz == null) {
+        // Skip this interface. If the lambda only implements missing library interfaces and not any
+        // program interfaces, then minification and tree shaking are not interested in this
+        // DexCallSite anyway, so skipping this interface is harmless. On the other hand, if
+        // minification is run on a program with a lambda interface that implements both a missing
+        // library interface and a present program interface, then we might minify the method name
+        // on the program interface even though it should be kept the same as the (missing) library
+        // interface method. That is a shame, but minification is not suited for incomplete programs
+        // anyway.
+        continue;
+      }
+      assert clazz.isInterface();
+      for (DexEncodedMethod method : clazz.virtualMethods()) {
+        if (method.method.name == callSite.methodName && method.accessFlags.isAbstract()) {
+          result.add(method);
+        }
+      }
+      Collections.addAll(worklist, clazz.interfaces.values);
+    }
+    return result;
+  }
+
+  // TODO(b/139464956): Reimplement using only reachable types.
+  public DexProgramClass getSingleDirectSubtype(DexProgramClass clazz) {
+    DexType subtype = super.getSingleSubtype_(clazz.type);
+    return subtype == null ? null : asProgramClassOrNull(definitionFor(subtype));
+  }
+
+  /**
+   * Apply the given function to all classes that directly extend this class.
+   *
+   * <p>If this class is an interface, then this method will visit all sub-interfaces. This deviates
+   * from the dex-file encoding, where subinterfaces "implement" their super interfaces. However, it
+   * is consistent with the source language.
+   */
+  // TODO(b/139464956): Reimplement using only reachable types.
+  public void forAllImmediateExtendsSubtypes(DexType type, Consumer<DexType> f) {
+    allImmediateExtendsSubtypes(type).forEach(f);
+  }
+
+  // TODO(b/139464956): Reimplement using only reachable types.
+  public Iterable<DexType> allImmediateExtendsSubtypes(DexType type) {
+    return super.allImmediateExtendsSubtypes_(type);
+  }
+
+  /**
+   * Apply the given function to all classes that directly implement this interface.
+   *
+   * <p>The implementation does not consider how the hierarchy is encoded in the dex file, where
+   * interfaces "implement" their super interfaces. Instead it takes the view of the source
+   * language, where interfaces "extend" their superinterface.
+   */
+  // TODO(b/139464956): Reimplement using only reachable types.
+  public void forAllImmediateImplementsSubtypes(DexType type, Consumer<DexType> f) {
+    allImmediateImplementsSubtypes(type).forEach(f);
+  }
+
+  // TODO(b/139464956): Reimplement using only reachable types.
+  public Iterable<DexType> allImmediateImplementsSubtypes(DexType type) {
+    return super.allImmediateImplementsSubtypes_(type);
+  }
+
+  /**
    * Const-classes is a conservative set of types that may be lock-candidates and cannot be merged.
    * When using synchronized blocks, we cannot ensure that const-class locks will not flow in. This
    * can potentially cause incorrect behavior when merging classes. A conservative choice is to not
@@ -641,28 +655,6 @@
     return constClassReferences.contains(type);
   }
 
-  public AppInfoWithLiveness withStaticFieldWrites(
-      Map<DexEncodedField, Set<DexEncodedMethod>> writesWithContexts) {
-    assert checkIfObsolete();
-    if (writesWithContexts.isEmpty()) {
-      return this;
-    }
-    AppInfoWithLiveness result = new AppInfoWithLiveness(this);
-    writesWithContexts.forEach(
-        (encodedField, contexts) -> {
-          DexField field = encodedField.field;
-          FieldAccessInfoImpl fieldAccessInfo = result.fieldAccessInfoCollection.get(field);
-          if (fieldAccessInfo == null) {
-            fieldAccessInfo = new FieldAccessInfoImpl(field);
-            result.fieldAccessInfoCollection.extend(field, fieldAccessInfo);
-          }
-          for (DexEncodedMethod context : contexts) {
-            fieldAccessInfo.recordWrite(field, context);
-          }
-        });
-    return result;
-  }
-
   public AppInfoWithLiveness withoutStaticFieldsWrites(Set<DexField> noLongerWrittenFields) {
     assert checkIfObsolete();
     if (noLongerWrittenFields.isEmpty()) {
@@ -682,13 +674,6 @@
     return result;
   }
 
-  private <T extends PresortedComparable<T>> SortedSet<T> filter(
-      Set<T> items, Predicate<T> predicate) {
-    return ImmutableSortedSet.copyOf(
-        PresortedComparable::slowCompareTo,
-        items.stream().filter(predicate).collect(Collectors.toList()));
-  }
-
   public Map<DexField, EnumValueInfo> getEnumValueInfoMapFor(DexType enumClass) {
     assert checkIfObsolete();
     return enumValueInfoMaps.get(enumClass);
@@ -941,17 +926,90 @@
    * DexApplication object.
    */
   public AppInfoWithLiveness prunedCopyFrom(
-      DexApplication application,
+      DirectMappedDexApplication application,
       Collection<DexType> removedClasses,
       Collection<DexReference> additionalPinnedItems) {
     assert checkIfObsolete();
     return new AppInfoWithLiveness(this, application, removedClasses, additionalPinnedItems);
   }
 
-  public AppInfoWithLiveness rewrittenWithLense(
-      DirectMappedDexApplication application, GraphLense lense) {
+  public AppInfoWithLiveness rewrittenWithLens(
+      DirectMappedDexApplication application, NestedGraphLense lens) {
     assert checkIfObsolete();
-    return new AppInfoWithLiveness(this, application, lense);
+    // The application has already been rewritten with all of lens' parent lenses. Therefore, we
+    // temporarily replace lens' parent lens with an identity lens to avoid the overhead of
+    // traversing the entire lens chain upon each lookup during the rewriting.
+    return lens.withAlternativeParentLens(
+        GraphLense.getIdentityLense(), () -> createRewrittenAppInfoWithLiveness(application, lens));
+  }
+
+  private AppInfoWithLiveness createRewrittenAppInfoWithLiveness(
+      DirectMappedDexApplication application, NestedGraphLense lens) {
+    // Switchmap classes should never be affected by renaming.
+    assert lens.assertDefinitionsNotModified(
+        switchMaps.keySet().stream()
+            .map(this::definitionFor)
+            .filter(Objects::nonNull)
+            .collect(Collectors.toList()));
+
+    assert lens.assertDefinitionsNotModified(
+        neverMerge.stream()
+            .map(this::definitionFor)
+            .filter(Objects::nonNull)
+            .collect(Collectors.toList()));
+
+    assert lens.assertDefinitionsNotModified(
+        alwaysInline.stream()
+            .map(this::definitionFor)
+            .filter(Objects::nonNull)
+            .collect(Collectors.toList()));
+
+    return new AppInfoWithLiveness(
+        application,
+        rewriteItems(liveTypes, lens::lookupType),
+        rewriteItems(instantiatedAnnotationTypes, lens::lookupType),
+        rewriteItems(instantiatedAppServices, lens::lookupType),
+        rewriteItems(instantiatedTypes, lens::lookupType),
+        lens.rewriteMethodsConservatively(targetedMethods),
+        lens.rewriteMethodsConservatively(failedResolutionTargets),
+        lens.rewriteMethodsConservatively(bootstrapMethods),
+        lens.rewriteMethodsConservatively(methodsTargetedByInvokeDynamic),
+        lens.rewriteMethodsConservatively(virtualMethodsTargetedByInvokeDirect),
+        lens.rewriteMethodsConservatively(liveMethods),
+        fieldAccessInfoCollection.rewrittenWithLens(application, lens),
+        rewriteKeysConservativelyWhileMergingValues(
+            virtualInvokes, lens::lookupMethodInAllContexts),
+        rewriteKeysConservativelyWhileMergingValues(
+            interfaceInvokes, lens::lookupMethodInAllContexts),
+        rewriteKeysConservativelyWhileMergingValues(superInvokes, lens::lookupMethodInAllContexts),
+        rewriteKeysConservativelyWhileMergingValues(directInvokes, lens::lookupMethodInAllContexts),
+        rewriteKeysConservativelyWhileMergingValues(staticInvokes, lens::lookupMethodInAllContexts),
+        // TODO(sgjesse): Rewrite call sites as well? Right now they are only used by minification
+        //   after second tree shaking.
+        callSites,
+        lens.rewriteReferencesConservatively(pinnedItems),
+        rewriteReferenceKeys(mayHaveSideEffects, lens::lookupReference),
+        rewriteReferenceKeys(noSideEffects, lens::lookupReference),
+        rewriteReferenceKeys(assumedValues, lens::lookupReference),
+        lens.rewriteMethodsWithRenamedSignature(alwaysInline),
+        lens.rewriteMethodsWithRenamedSignature(forceInline),
+        lens.rewriteMethodsWithRenamedSignature(neverInline),
+        lens.rewriteMethodsWithRenamedSignature(whyAreYouNotInlining),
+        lens.rewriteMethodsWithRenamedSignature(keepConstantArguments),
+        lens.rewriteMethodsWithRenamedSignature(keepUnusedArguments),
+        lens.rewriteMethodsWithRenamedSignature(reprocess),
+        lens.rewriteMethodsWithRenamedSignature(neverReprocess),
+        alwaysClassInline.rewriteItems(lens::lookupType),
+        rewriteItems(neverClassInline, lens::lookupType),
+        rewriteItems(neverMerge, lens::lookupType),
+        lens.rewriteReferencesConservatively(neverPropagateValue),
+        lens.rewriteReferencesConservatively(identifierNameStrings),
+        // Don't rewrite pruned types - the removed types are identified by their original name.
+        prunedTypes,
+        rewriteReferenceKeys(switchMaps, lens::lookupField),
+        rewriteReferenceKeys(enumValueInfoMaps, lens::lookupType),
+        rewriteItems(instantiatedLambdas, lens::lookupType),
+        constClassReferences);
   }
 
   /**
@@ -1343,4 +1401,78 @@
     assert this.enumValueInfoMaps.isEmpty();
     return new AppInfoWithLiveness(this, switchMaps, enumValueInfoMaps);
   }
+
+  public void forEachLiveProgramClass(Consumer<DexProgramClass> fn) {
+    for (DexType type : liveTypes) {
+      fn.accept(definitionFor(type).asProgramClass());
+    }
+  }
+
+  /**
+   * Visit all class definitions of classpath classes that are referenced in the compilation unit.
+   *
+   * <p>TODO(b/139464956): Only traverse the classpath types referenced from the live program.
+   * Conservatively traces all classpath classes for now.
+   */
+  public void forEachReferencedClasspathClass(Consumer<DexClasspathClass> fn) {
+    app().asDirect().classpathClasses().forEach(fn);
+  }
+
+  /**
+   * Visits all class definitions that are a live program type or a type above it in the hierarchy.
+   *
+   * <p>Any given definition will be visited at most once. No guarantees are places on the order.
+   */
+  public void forEachTypeInHierarchyOfLiveProgramClasses(Consumer<DexClass> fn) {
+    forEachTypeInHierarchyOfLiveProgramClasses(
+        fn, ListUtils.map(liveTypes, t -> definitionFor(t).asProgramClass()), callSites, this);
+  }
+
+  // Split in a static method so it can be used during construction.
+  static void forEachTypeInHierarchyOfLiveProgramClasses(
+      Consumer<DexClass> fn,
+      Collection<DexProgramClass> liveProgramClasses,
+      Set<DexCallSite> callSites,
+      AppInfoWithClassHierarchy appInfo) {
+    Set<DexType> seen = Sets.newIdentityHashSet();
+    Deque<DexType> worklist = new ArrayDeque<>();
+    liveProgramClasses.forEach(c -> seen.add(c.type));
+    for (DexProgramClass liveProgramClass : liveProgramClasses) {
+      fn.accept(liveProgramClass);
+      DexType superType = liveProgramClass.superType;
+      if (superType != null && seen.add(superType)) {
+        worklist.add(superType);
+      }
+      for (DexType iface : liveProgramClass.interfaces.values) {
+        if (seen.add(iface)) {
+          worklist.add(iface);
+        }
+      }
+    }
+    for (DexCallSite callSite : callSites) {
+      List<DexType> interfaces = LambdaDescriptor.getInterfaces(callSite, appInfo);
+      if (interfaces != null) {
+        for (DexType iface : interfaces) {
+          if (seen.add(iface)) {
+            worklist.add(iface);
+          }
+        }
+      }
+    }
+    while (!worklist.isEmpty()) {
+      DexType type = worklist.pop();
+      DexClass clazz = appInfo.definitionFor(type);
+      if (clazz != null) {
+        fn.accept(clazz);
+        if (clazz.superType != null && seen.add(clazz.superType)) {
+          worklist.add(clazz.superType);
+        }
+        for (DexType iface : clazz.interfaces.values) {
+          if (seen.add(iface)) {
+            worklist.add(iface);
+          }
+        }
+      }
+    }
+  }
 }
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 f8cdb36..ba1bdb5 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -28,10 +28,9 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Descriptor;
 import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -46,6 +45,7 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
 import com.android.tools.r8.graph.FieldAccessInfoImpl;
 import com.android.tools.r8.graph.KeyedDexItem;
@@ -55,7 +55,9 @@
 import com.android.tools.r8.graph.ResolutionResult.FailedResolutionResult;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
+import com.android.tools.r8.graph.analysis.DesugaredLibraryConversionWrapperAnalysis;
 import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerInvokeAnalysis;
 import com.android.tools.r8.ir.analysis.proto.ProtoEnqueuerUseRegistry;
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoEnqueuerExtension;
 import com.android.tools.r8.ir.code.ArrayPut;
@@ -155,6 +157,7 @@
   private final Mode mode;
 
   private Set<EnqueuerAnalysis> analyses = Sets.newIdentityHashSet();
+  private Set<EnqueuerInvokeAnalysis> invokeAnalyses = Sets.newIdentityHashSet();
   private final AppInfoWithSubtyping appInfo;
   private final AppView<? extends AppInfoWithSubtyping> appView;
   private final InternalOptions options;
@@ -235,10 +238,8 @@
    * Set of direct methods that are the immediate target of an invoke-dynamic.
    */
   private final Set<DexMethod> methodsTargetedByInvokeDynamic = Sets.newIdentityHashSet();
-  /**
-   * Set of direct lambda methods that are the immediate target of an invoke-dynamic.
-   */
-  private final Set<DexMethod> lambdaMethodsTargetedByInvokeDynamic = Sets.newIdentityHashSet();
+  /** Set of direct lambda implementation methods that have been desugared, thus they may move. */
+  private final Set<DexMethod> desugaredLambdaImplementationMethods = Sets.newIdentityHashSet();
   /**
    * Set of virtual methods that are the immediate target of an invoke-direct.
    */
@@ -317,6 +318,7 @@
   private final GraphReporter graphReporter;
 
   private final LambdaRewriter lambdaRewriter;
+  private final DesugaredLibraryConversionWrapperAnalysis desugaredLibraryWrapperAnalysis;
   private final Map<DexType, LambdaClass> lambdaClasses = new IdentityHashMap<>();
   private final Map<DexEncodedMethod, Map<DexCallSite, LambdaClass>> lambdaCallSites =
       new IdentityHashMap<>();
@@ -356,6 +358,14 @@
     unknownInstantiatedInterfaceTypes = new SetWithReason<>(graphReporter::registerInterface);
     instantiatedInterfaceTypes = Sets.newIdentityHashSet();
     lambdaRewriter = options.desugarState == DesugarState.ON ? new LambdaRewriter(appView) : null;
+
+    if (appView.rewritePrefix.isRewriting() && mode.isInitialTreeShaking()) {
+      desugaredLibraryWrapperAnalysis = new DesugaredLibraryConversionWrapperAnalysis(appView);
+      registerAnalysis(desugaredLibraryWrapperAnalysis);
+      registerInvokeAnalysis(desugaredLibraryWrapperAnalysis);
+    } else {
+      desugaredLibraryWrapperAnalysis = null;
+    }
   }
 
   public Mode getMode() {
@@ -379,7 +389,12 @@
   }
 
   public Enqueuer registerAnalysis(EnqueuerAnalysis analysis) {
-    this.analyses.add(analysis);
+    analyses.add(analysis);
+    return this;
+  }
+
+  private Enqueuer registerInvokeAnalysis(EnqueuerInvokeAnalysis analysis) {
+    invokeAnalyses.add(analysis);
     return this;
   }
 
@@ -639,6 +654,9 @@
           classesWithSerializableLambdas.add(context.holder);
         }
       }
+      if (descriptor.delegatesToLambdaImplMethod()) {
+        desugaredLambdaImplementationMethods.add(descriptor.implHandle.asMethod());
+      }
     } else {
       callSites.add(callSite);
     }
@@ -650,10 +668,6 @@
     assert implHandle != null;
 
     DexMethod method = implHandle.asMethod();
-    if (descriptor.delegatesToLambdaImplMethod()) {
-      lambdaMethodsTargetedByInvokeDynamic.add(method);
-    }
-
     if (!methodsTargetedByInvokeDynamic.add(method)) {
       return;
     }
@@ -808,6 +822,7 @@
       Log.verbose(getClass(), "Register invokeDirect `%s`.", invokedMethod);
     }
     handleInvokeOfDirectTarget(invokedMethod, reason);
+    invokeAnalyses.forEach(analysis -> analysis.traceInvokeDirect(invokedMethod, context));
     return true;
   }
 
@@ -831,6 +846,7 @@
       Log.verbose(getClass(), "Register invokeInterface `%s`.", method);
     }
     markVirtualMethodAsReachable(method, true, context, keepReason);
+    invokeAnalyses.forEach(analysis -> analysis.traceInvokeInterface(method, context));
     return true;
   }
 
@@ -873,6 +889,7 @@
       Log.verbose(getClass(), "Register invokeStatic `%s`.", invokedMethod);
     }
     handleInvokeOfStaticTarget(invokedMethod, reason);
+    invokeAnalyses.forEach(analysis -> analysis.traceInvokeStatic(invokedMethod, context));
     return true;
   }
 
@@ -889,6 +906,7 @@
       Log.verbose(getClass(), "Register invokeSuper `%s`.", actualTarget);
     }
     workList.enqueueMarkReachableSuperAction(invokedMethod, currentMethod);
+    invokeAnalyses.forEach(analysis -> analysis.traceInvokeSuper(invokedMethod, context));
     return true;
   }
 
@@ -920,6 +938,7 @@
       Log.verbose(getClass(), "Register invokeVirtual `%s`.", invokedMethod);
     }
     markVirtualMethodAsReachable(invokedMethod, false, context, reason);
+    invokeAnalyses.forEach(analysis -> analysis.traceInvokeVirtual(invokedMethod, context));
     return true;
   }
 
@@ -1661,6 +1680,7 @@
       ResolutionResult firstResolution =
           appView.appInfo().resolveMethod(instantiatedClass, method.method);
       markResolutionAsLive(libraryClass, firstResolution);
+      markOverridesAsLibraryMethodOverrides(method.method, instantiatedClass);
 
       // Due to API conversion, some overrides can be hidden since they will be rewritten. See
       // class comment of DesugaredLibraryAPIConverter and vivifiedType logic.
@@ -1675,9 +1695,9 @@
         ResolutionResult secondResolution =
             appView.appInfo().resolveMethod(instantiatedClass, methodToResolve);
         markResolutionAsLive(libraryClass, secondResolution);
+        markOverridesAsLibraryMethodOverrides(methodToResolve, instantiatedClass);
       }
 
-      markOverridesAsLibraryMethodOverrides(method, instantiatedClass);
     }
   }
 
@@ -1694,13 +1714,13 @@
   }
 
   private void markOverridesAsLibraryMethodOverrides(
-      DexEncodedMethod libraryMethod, DexProgramClass instantiatedClass) {
+      DexMethod libraryMethod, DexProgramClass instantiatedClass) {
     Set<DexProgramClass> visited = SetUtils.newIdentityHashSet(instantiatedClass);
     Deque<DexProgramClass> worklist = DequeUtils.newArrayDeque(instantiatedClass);
     while (!worklist.isEmpty()) {
       DexProgramClass clazz = worklist.removeFirst();
       assert visited.contains(clazz);
-      DexEncodedMethod libraryMethodOverride = clazz.lookupVirtualMethod(libraryMethod.method);
+      DexEncodedMethod libraryMethodOverride = clazz.lookupVirtualMethod(libraryMethod);
       if (libraryMethodOverride != null) {
         if (libraryMethodOverride.isLibraryMethodOverride().isTrue()) {
           continue;
@@ -2048,7 +2068,7 @@
     Set<DexEncodedMethod> possibleTargets =
         // TODO(b/140214802): Call on the resolution once proper resolution and lookup is resolved.
         new SingleResolutionResult(holder, resolution.holder, resolution.method)
-            .lookupVirtualDispatchTargets(interfaceInvoke, appInfo);
+            .lookupVirtualDispatchTargets(appInfo);
     if (possibleTargets == null || possibleTargets.isEmpty()) {
       return;
     }
@@ -2310,7 +2330,12 @@
       liveMethods.add(bridge.holder, bridge.method, graphReporter.fakeReportShouldNotBeUsed());
     }
 
-    DexApplication app = postProcessLambdaDesugaring(appInfo);
+    // A direct appInfo is required to add classpath classes in wrapper post processing.
+    assert appInfo.app().isDirect() : "Expected a direct appInfo after enqueuing.";
+    DirectMappedDexApplication.Builder appBuilder = appInfo.app().asDirect().builder();
+    postProcessLambdaDesugaring(appBuilder);
+    postProcessLibraryConversionWrappers(appBuilder);
+    DirectMappedDexApplication app = appBuilder.build();
 
     AppInfoWithLiveness appInfoWithLiveness =
         new AppInfoWithLiveness(
@@ -2363,11 +2388,51 @@
     return appInfoWithLiveness;
   }
 
-  private DexApplication postProcessLambdaDesugaring(AppInfoWithSubtyping appInfo) {
-    if (lambdaRewriter == null || lambdaClasses.isEmpty()) {
-      return appInfo.app();
+  private void postProcessLibraryConversionWrappers(DirectMappedDexApplication.Builder appBuilder) {
+    if (desugaredLibraryWrapperAnalysis == null) {
+      return;
     }
-    Builder<?> appBuilder = appInfo.app().builder();
+
+    // Generate first the callbacks since they may require extra wrappers.
+    List<DexEncodedMethod> callbacks = desugaredLibraryWrapperAnalysis.generateCallbackMethods();
+    for (DexEncodedMethod callback : callbacks) {
+      DexProgramClass clazz = getProgramClassOrNull(callback.method.holder);
+      targetedMethods.add(callback, graphReporter.fakeReportShouldNotBeUsed());
+      liveMethods.add(clazz, callback, graphReporter.fakeReportShouldNotBeUsed());
+    }
+
+    // Generate the wrappers.
+    Set<DexProgramClass> wrappers = desugaredLibraryWrapperAnalysis.generateWrappers();
+    for (DexProgramClass wrapper : wrappers) {
+      appBuilder.addProgramClass(wrapper);
+      liveTypes.add(wrapper, graphReporter.fakeReportShouldNotBeUsed());
+      instantiatedTypes.add(wrapper, graphReporter.fakeReportShouldNotBeUsed());
+      // Mark all methods on the wrapper as live and targeted.
+      for (DexEncodedMethod method : wrapper.methods()) {
+        targetedMethods.add(method, graphReporter.fakeReportShouldNotBeUsed());
+        liveMethods.add(wrapper, method, graphReporter.fakeReportShouldNotBeUsed());
+      }
+      // Register wrapper unique field reads and unique write.
+      assert wrapper.instanceFields().size() == 1;
+      DexField field = wrapper.instanceFields().get(0).field;
+      FieldAccessInfoImpl info = new FieldAccessInfoImpl(field);
+      fieldAccessInfoCollection.extend(field, info);
+      desugaredLibraryWrapperAnalysis
+          .registerWrite(wrapper, writeContext -> info.recordWrite(field, writeContext))
+          .registerReads(wrapper, readContext -> info.recordRead(field, readContext));
+    }
+
+    // Add all vivified types as classpath classes.
+    // They will be available at runtime in the desugared library dex file.
+    List<DexClasspathClass> mockVivifiedClasses =
+        desugaredLibraryWrapperAnalysis.generateWrappersSuperTypeMock();
+    appBuilder.addClasspathClasses(mockVivifiedClasses);
+  }
+
+  private void postProcessLambdaDesugaring(DirectMappedDexApplication.Builder appBuilder) {
+    if (lambdaRewriter == null || lambdaClasses.isEmpty()) {
+      return;
+    }
     for (LambdaClass lambdaClass : lambdaClasses.values()) {
       // Add all desugared classes to the application, main-dex list, and mark them instantiated.
       DexProgramClass programClass = lambdaClass.getOrCreateLambdaClass();
@@ -2411,8 +2476,6 @@
     for (DexProgramClass clazz : classesWithSerializableLambdas) {
       clazz.removeDirectMethod(appView.dexItemFactory().deserializeLambdaMethod);
     }
-
-    return appBuilder.build();
   }
 
   private void rewriteLambdaCallSites(
@@ -2644,11 +2707,13 @@
   }
 
   private void unpinLambdaMethods() {
-    for (DexMethod method : lambdaMethodsTargetedByInvokeDynamic) {
+    assert desugaredLambdaImplementationMethods.isEmpty()
+        || options.desugarState == DesugarState.ON;
+    for (DexMethod method : desugaredLambdaImplementationMethods) {
       pinnedItems.remove(method);
       rootSet.prune(method);
     }
-    lambdaMethodsTargetedByInvokeDynamic.clear();
+    desugaredLambdaImplementationMethods.clear();
   }
 
   // Package protected due to entry point from worklist.
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
index bfefefa..718b9a9 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
@@ -6,12 +6,12 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Maps;
 import java.util.Map;
@@ -29,14 +29,14 @@
   private final Set<DexType> roots;
   private final AppInfoWithSubtyping appInfo;
   private final Map<DexType, Boolean> annotationTypeContainEnum;
-  private final DexApplication dexApplication;
+  private final DirectMappedDexApplication dexApplication;
   private final MainDexClasses.Builder mainDexClassesBuilder;
 
   /**
    * @param roots Classes which code may be executed before secondary dex files loading.
    * @param application the dex appplication.
    */
-  public MainDexListBuilder(Set<DexProgramClass> roots, DexApplication application) {
+  public MainDexListBuilder(Set<DexProgramClass> roots, DirectMappedDexApplication application) {
     this.dexApplication = application;
     this.appInfo = new AppInfoWithSubtyping(dexApplication);
     // Only consider program classes for the root set.
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index bae5b40..b89678e 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.VerticalClassMerger.IllegalAccessDetector;
@@ -225,7 +224,7 @@
     this.mainDexClasses = mainDexClasses;
   }
 
-  public GraphLense run() {
+  public NestedGraphLense run() {
     for (DexProgramClass clazz : appView.appInfo().app().classesWithDeterministicOrder()) {
       MergeGroup group = satisfiesMergeCriteria(clazz);
       if (group != MergeGroup.DONT_MERGE) {
@@ -242,7 +241,7 @@
     return buildGraphLense();
   }
 
-  private GraphLense buildGraphLense() {
+  private NestedGraphLense buildGraphLense() {
     if (!fieldMapping.isEmpty() || !methodMapping.isEmpty()) {
       BiMap<DexField, DexField> originalFieldSignatures = fieldMapping.inverse();
       BiMap<DexMethod, DexMethod> originalMethodSignatures = methodMapping.inverse();
@@ -255,7 +254,7 @@
           appView.graphLense(),
           appView.dexItemFactory());
     }
-    return appView.graphLense();
+    return null;
   }
 
   private MergeGroup satisfiesMergeCriteria(DexProgramClass clazz) {
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 116e315..dc66249 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -13,6 +12,7 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.KeyedDexItem;
@@ -56,10 +56,10 @@
             : UsagePrinter.DONT_PRINT;
   }
 
-  public DexApplication run(DexApplication application) {
+  public DirectMappedDexApplication run(DirectMappedDexApplication application) {
     Timing timing = application.timing;
     timing.begin("Pruning application...");
-    DexApplication result;
+    DirectMappedDexApplication result;
     try {
       result = removeUnused(application).build();
     } finally {
@@ -68,7 +68,7 @@
     return result;
   }
 
-  private DexApplication.Builder<?> removeUnused(DexApplication application) {
+  private DirectMappedDexApplication.Builder removeUnused(DirectMappedDexApplication application) {
     return application.builder()
         .replaceProgramClasses(getNewProgramClasses(application.classes()));
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 36a7030..8217920 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -345,22 +345,21 @@
 
     // Note that the property "singleSubtype == null" cannot change during merging, since we visit
     // classes in a top-down order.
-    DexType singleSubtype = appInfo.getSingleSubtype(clazz.type);
+    DexProgramClass singleSubtype = appInfo.getSingleDirectSubtype(clazz);
     if (singleSubtype == null) {
       // TODO(christofferqa): Even if [clazz] has multiple subtypes, we could still merge it into
       // its subclass if [clazz] is not live. This should only be done, though, if it does not
       // lead to members being duplicated.
       return false;
     }
-    if (singleSubtype != null
-        && appView.appServices().allServiceTypes().contains(clazz.type)
-        && appInfo.isPinned(singleSubtype)) {
+    if (appView.appServices().allServiceTypes().contains(clazz.type)
+        && appInfo.isPinned(singleSubtype.type)) {
       if (Log.ENABLED) {
         AbortReason.SERVICE_LOADER.printLogMessageForClass(clazz);
       }
       return false;
     }
-    if (appInfo.isSerializable(singleSubtype) && !appInfo.isSerializable(clazz.type)) {
+    if (singleSubtype.isSerializable(appView) && !appInfo.isSerializable(clazz.type)) {
       // https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html
       //   1.10 The Serializable Interface
       //   ...
@@ -373,7 +372,7 @@
       // We rename constructors to private methods and mark them to be forced-inlined, so we have to
       // check if we can force-inline all constructors.
       if (method.isInstanceInitializer()) {
-        AbortReason reason = disallowInlining(method, singleSubtype);
+        AbortReason reason = disallowInlining(method, singleSubtype.type);
         if (reason != null) {
           // Cannot guarantee that markForceInline() will work.
           if (Log.ENABLED) {
@@ -390,10 +389,9 @@
       }
       return false;
     }
-    DexClass targetClass = appView.definitionFor(singleSubtype);
     // We abort class merging when merging across nests or from a nest to non-nest.
     // Without nest this checks null == null.
-    if (targetClass.getNestHost() != clazz.getNestHost()) {
+    if (singleSubtype.getNestHost() != clazz.getNestHost()) {
       if (Log.ENABLED) {
         AbortReason.MERGE_ACROSS_NESTS.printLogMessageForClass(clazz);
       }
@@ -415,7 +413,7 @@
       }
       return false;
     }
-    DexClass targetClass = appInfo.definitionFor(appInfo.getSingleSubtype(clazz.type));
+    DexProgramClass targetClass = appInfo.getSingleDirectSubtype(clazz);
     // For interface types, this is more complicated, see:
     // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-5.html#jvms-5.5
     // We basically can't move the clinit, since it is not called when implementing classes have
@@ -533,7 +531,8 @@
 
     public OverloadedMethodSignaturesRetriever() {
       for (DexProgramClass mergeCandidate : mergeCandidates) {
-        mergeeCandidates.add(appInfo.getSingleSubtype(mergeCandidate.type));
+        DexProgramClass candidate = appInfo.getSingleDirectSubtype(mergeCandidate);
+        mergeeCandidates.add(candidate.type);
       }
     }
 
@@ -614,7 +613,7 @@
     }
   }
 
-  public GraphLense run() {
+  public VerticalClassMergerGraphLense run() {
     timing.begin("merge");
     // Visit the program classes in a top-down order according to the class hierarchy.
     TopDownClassHierarchyTraversal.forProgramClasses(appView)
@@ -624,18 +623,19 @@
     }
     timing.end();
     timing.begin("fixup");
-    GraphLense result = new TreeFixer().fixupTypeReferences();
+    VerticalClassMergerGraphLense lens = new TreeFixer().fixupTypeReferences();
     timing.end();
-    assert result.assertDefinitionsNotModified(
+    assert lens == null || verifyGraphLens(lens);
+    return lens;
+  }
+
+  private boolean verifyGraphLens(VerticalClassMergerGraphLense graphLense) {
+    assert graphLense.assertDefinitionsNotModified(
         appInfo.alwaysInline.stream()
             .map(appInfo::definitionFor)
             .filter(Objects::nonNull)
             .collect(Collectors.toList()));
-    assert verifyGraphLense(result);
-    return result;
-  }
 
-  private boolean verifyGraphLense(GraphLense graphLense) {
     assert graphLense.assertReferencesNotModified(appInfo.noSideEffects.keySet());
 
     // Note that the method assertReferencesNotModified() relies on getRenamedFieldSignature() and
@@ -737,7 +737,7 @@
         Set<DexEncodedMethod> interfaceTargets =
             appInfo
                 .resolveMethodOnInterface(method.method.holder, method.method)
-                .lookupInterfaceTargets(appInfo);
+                .lookupVirtualDispatchTargets(appInfo);
 
         // If [method] is not even an interface-target, then we can safely merge it. Otherwise we
         // need to check for a conflict.
@@ -762,8 +762,7 @@
 
     assert isMergeCandidate(clazz, pinnedTypes);
 
-    DexProgramClass targetClass =
-        appInfo.definitionFor(appInfo.getSingleSubtype(clazz.type)).asProgramClass();
+    DexProgramClass targetClass = appInfo.getSingleDirectSubtype(clazz);
     assert !mergedClasses.containsKey(targetClass.type);
 
     boolean clazzOrTargetClassHasBeenMerged =
@@ -1430,7 +1429,7 @@
             renamedMembersLense, mergedClasses);
     private final Map<DexProto, DexProto> protoFixupCache = new IdentityHashMap<>();
 
-    private GraphLense fixupTypeReferences() {
+    private VerticalClassMergerGraphLense fixupTypeReferences() {
       // Globally substitute merged class types in protos and holders.
       for (DexProgramClass clazz : appInfo.classes()) {
         fixupMethods(clazz.directMethods(), clazz::setDirectMethod);
@@ -1441,9 +1440,11 @@
       for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
         synthesizedBridge.updateMethodSignatures(this::fixupMethod);
       }
-      GraphLense graphLense = lensBuilder.build(appView, mergedClasses);
-      new AnnotationFixer(graphLense).run(appView.appInfo().classes());
-      return graphLense;
+      VerticalClassMergerGraphLense lens = lensBuilder.build(appView, mergedClasses);
+      if (lens != null) {
+        new AnnotationFixer(lens).run(appView.appInfo().classes());
+      }
+      return lens;
     }
 
     private void fixupMethods(List<DexEncodedMethod> methods, MethodSetter setter) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
index a265d99..eded127 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -16,6 +16,7 @@
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
@@ -53,6 +54,8 @@
   private Set<DexMethod> mergedMethods;
   private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges;
 
+  private Map<DexMethod, Set<DexType>> contextsForContextSensitiveMethods;
+
   private VerticalClassMergerGraphLense(
       AppView<?> appView,
       Map<DexType, DexType> typeMap,
@@ -78,6 +81,24 @@
     this.originalMethodSignaturesForBridges = originalMethodSignaturesForBridges;
   }
 
+  public void initializeCacheForLookupMethodInAllContexts() {
+    assert contextsForContextSensitiveMethods == null;
+    contextsForContextSensitiveMethods = new IdentityHashMap<>();
+    contextualVirtualToDirectMethodMaps.forEach(
+        (type, virtualToDirectMethodMap) -> {
+          for (DexMethod method : virtualToDirectMethodMap.keySet()) {
+            contextsForContextSensitiveMethods
+                .computeIfAbsent(method, ignore -> Sets.newIdentityHashSet())
+                .add(type);
+          }
+        });
+  }
+
+  public void unsetCacheForLookupMethodInAllContexts() {
+    assert contextsForContextSensitiveMethods != null;
+    contextsForContextSensitiveMethods = null;
+  }
+
   @Override
   public DexType getOriginalType(DexType type) {
     return previousLense.getOriginalType(type);
@@ -91,7 +112,8 @@
 
   @Override
   public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
-    assert isContextFreeForMethod(method) || (context != null && type != null);
+    assert context != null || verifyIsContextFreeForMethod(method);
+    assert context == null || type != null;
     DexMethod previousContext =
         originalMethodSignaturesForBridges.containsKey(context)
             ? originalMethodSignaturesForBridges.get(context)
@@ -123,14 +145,14 @@
 
   @Override
   public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
+    assert contextsForContextSensitiveMethods != null;
     ImmutableSet.Builder<DexMethod> builder = ImmutableSet.builder();
     for (DexMethod previous : previousLense.lookupMethodInAllContexts(method)) {
       builder.add(methodMap.getOrDefault(previous, previous));
-      for (Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap :
-          contextualVirtualToDirectMethodMaps.values()) {
-        GraphLenseLookupResult lookup = virtualToDirectMethodMap.get(previous);
-        if (lookup != null) {
-          builder.add(lookup.getMethod());
+      Set<DexType> contexts = contextsForContextSensitiveMethods.get(previous);
+      if (contexts != null) {
+        for (DexType context : contexts) {
+          builder.add(contextualVirtualToDirectMethodMaps.get(context).get(previous).getMethod());
         }
       }
     }
@@ -143,17 +165,11 @@
   }
 
   @Override
-  public boolean isContextFreeForMethod(DexMethod method) {
-    if (!previousLense.isContextFreeForMethod(method)) {
-      return false;
-    }
+  public boolean verifyIsContextFreeForMethod(DexMethod method) {
+    assert previousLense.verifyIsContextFreeForMethod(method);
     DexMethod previous = previousLense.lookupMethod(method);
-    for (Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap :
-        contextualVirtualToDirectMethodMaps.values()) {
-      if (virtualToDirectMethodMap.containsKey(previous)) {
-        return false;
-      }
-    }
+    assert contextualVirtualToDirectMethodMaps.values().stream()
+        .noneMatch(virtualToDirectMethodMap -> virtualToDirectMethodMap.containsKey(previous));
     return true;
   }
 
@@ -221,9 +237,10 @@
       return newBuilder;
     }
 
-    public GraphLense build(AppView<?> appView, Map<DexType, DexType> mergedClasses) {
+    public VerticalClassMergerGraphLense build(
+        AppView<?> appView, Map<DexType, DexType> mergedClasses) {
       if (mergedClasses.isEmpty()) {
-        return appView.graphLense();
+        return null;
       }
       BiMap<DexField, DexField> originalFieldSignatures = fieldMap.inverse();
       // Build new graph lense.
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 70d6df0..51ab446 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -8,6 +8,7 @@
 import static com.android.tools.r8.utils.FileUtils.MODULES_PREFIX;
 
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.InvalidDescriptorException;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -370,6 +371,12 @@
     return 'L' + className.replace(JAVA_PACKAGE_SEPARATOR, INNER_CLASS_SEPARATOR) + ';';
   }
 
+  // Kotlin @Metadata deserialization has plain "kotlin", which will be relocated in r8lib.
+  // See b/70169921#comment25 for more details.
+  private static String backwardRelocatedName(String name) {
+    return name.replace("com/android/tools/r8/jetbrains/", "");
+  }
+
   /**
    * Get a fully qualified name from a classifier in Kotlin metadata.
    * @param kmType where classifier contains Kotlin internal name, like "org/foo/bar/Baz.Nested"
@@ -385,16 +392,16 @@
       public void visitClass(String name) {
         // TODO(b/70169921): Remove this if metadata lib is resilient to namespace relocation.
         //  See b/70169921#comment25 for more details.
-        String backwardRelocatedName = name.replace("com/android/tools/r8/jetbrains/", "");
-        descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName));
+        assert descriptor.get() == null : descriptor.get();
+        descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName(name)));
       }
 
       @Override
       public void visitTypeAlias(String name) {
         // TODO(b/70169921): Remove this if metadata lib is resilient to namespace relocation.
         //  See b/70169921#comment25 for more details.
-        String backwardRelocatedName = name.replace("com/android/tools/r8/jetbrains/", "");
-        descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName));
+        assert descriptor.get() == null : descriptor.get();
+        descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName(name)));
       }
     });
     return descriptor.get();
@@ -619,7 +626,7 @@
     while ((c = methodDescriptor.charAt(charIdx)) != ')') {
       switch (c) {
         case 'V':
-          throw new Unreachable();
+          throw new InvalidDescriptorException(methodDescriptor);
         case 'Z':
         case 'C':
         case 'B':
@@ -632,7 +639,8 @@
           break;
         case '[':
           startType = charIdx;
-          while (methodDescriptor.charAt(++charIdx) == '[') {}
+          while (methodDescriptor.charAt(++charIdx) == '[')
+            ;
           if (methodDescriptor.charAt(charIdx) == 'L') {
             while (methodDescriptor.charAt(++charIdx) != ';')
               ;
@@ -646,7 +654,7 @@
           argDescriptors[argIdx++] = methodDescriptor.substring(startType, charIdx + 1);
           break;
         default:
-          throw new Unreachable();
+          throw new InvalidDescriptorException(methodDescriptor);
       }
       charIdx++;
     }
@@ -654,18 +662,27 @@
   }
 
   public static int getArgumentCount(final String methodDescriptor) {
+    int length = methodDescriptor.length();
     int charIdx = 1;
     char c;
     int argCount = 0;
-    while ((c = methodDescriptor.charAt(charIdx++)) != ')') {
+    while (charIdx < length && (c = methodDescriptor.charAt(charIdx++)) != ')') {
       if (c == 'L') {
-        while (methodDescriptor.charAt(charIdx++) != ';')
+        while (charIdx < length && methodDescriptor.charAt(charIdx++) != ';')
           ;
+        // Check if the inner loop found ';' within the boundary.
+        if (charIdx >= length || methodDescriptor.charAt(charIdx - 1) != ';') {
+          throw new InvalidDescriptorException(methodDescriptor);
+        }
         argCount++;
       } else if (c != '[') {
         argCount++;
       }
     }
+    // Check if the outer loop found ')' within the boundary.
+    if (charIdx >= length || methodDescriptor.charAt(charIdx - 1) != ')') {
+      throw new InvalidDescriptorException(methodDescriptor);
+    }
     return argCount;
   }
 }
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 5b0400f..c6330b1 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -224,7 +224,11 @@
   public boolean enablePropagationOfDynamicTypesAtCallSites = true;
   // TODO(b/69963623): enable if everything is ready, including signature rewriting at call sites.
   public boolean enablePropagationOfConstantsAtCallSites = false;
-  public boolean enableKotlinMetadataRewriting = true;
+  // At 2.0, part of @Metadata up to this flag is rewritten, which is super-type hierarchy.
+  public boolean enableKotlinMetadataRewritingForMembers = true;
+  // Up to 2.0, Kotlin @Metadata is removed if the associated class is renamed.
+  // Under this flag, Kotlin @Metadata is generally kept and modified for all program classes.
+  public boolean enableKotlinMetadataRewritingForRenamedClasses = true;
   public boolean encodeChecksums = false;
   public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
   public boolean enableCfInterfaceMethodDesugaring = false;
@@ -258,6 +262,8 @@
   // TODO(b/138917494): Disable until we have numbers on potential performance penalties.
   public boolean enableRedundantConstNumberOptimization = false;
 
+  public boolean enablePcDebugInfoOutput = false;
+
   // Number of threads to use while processing the dex files.
   public int numberOfThreads = DETERMINISTIC_DEBUGGING ? 1 : ThreadUtils.NOT_SPECIFIED;
   // Print smali disassembly.
@@ -1018,7 +1024,6 @@
     public boolean placeExceptionalBlocksLast = false;
     public boolean dontCreateMarkerInD8 = false;
     public boolean forceJumboStringProcessing = false;
-    public boolean nondeterministicCycleElimination = false;
     public Set<Inliner.Reason> validInliningReasons = null;
     public boolean noLocalsTableOnInput = false;
     public boolean forceNameReflectionOptimization = false;
@@ -1143,6 +1148,11 @@
     enablePropagationOfDynamicTypesAtCallSites = false;
   }
 
+  public boolean canUseDexPcAsDebugInformation() {
+    // TODO(b/37830524): Enable for min-api 26 (OREO) and above.
+    return enablePcDebugInfoOutput;
+  }
+
   public boolean isInterfaceMethodDesugaringEnabled() {
     // This condition is to filter out tests that never set program consumer.
     if (!hasConsumer()) {
@@ -1162,6 +1172,10 @@
     return intermediate || hasMinApi(AndroidApiLevel.L);
   }
 
+  public boolean canUseRequireNonNull() {
+    return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.K);
+  }
+
   public boolean canUseSuppressedExceptions() {
     return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.K);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index e9eb31f..7bcf1c9 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -328,8 +328,12 @@
           Code code = method.getCode();
           if (code != null) {
             if (code.isDexCode() && doesContainPositions(code.asDexCode())) {
-              optimizeDexCodePositions(
-                  method, application, kotlinRemapper, mappedPositions, identityMapping);
+              if (appView.options().canUseDexPcAsDebugInformation() && methods.size() == 1) {
+                optimizeDexCodePositionsForPc(method, kotlinRemapper, mappedPositions);
+              } else {
+                optimizeDexCodePositions(
+                    method, appView, kotlinRemapper, mappedPositions, identityMapping);
+              }
             } else if (code.isCfCode() && doesContainPositions(code.asCfCode())) {
               optimizeCfCodePositions(method, kotlinRemapper, mappedPositions, appView);
             }
@@ -548,11 +552,12 @@
 
   private static void optimizeDexCodePositions(
       DexEncodedMethod method,
-      DexApplication application,
+      AppView<?> appView,
       PositionRemapper positionRemapper,
       List<MappedPosition> mappedPositions,
       boolean identityMapping) {
     // Do the actual processing for each method.
+    final DexApplication application = appView.appInfo().app();
     DexCode dexCode = method.getCode().asDexCode();
     DexDebugInfo debugInfo = dexCode.getDebugInfo();
     List<DexDebugEvent> processedEvents = new ArrayList<>();
@@ -589,10 +594,10 @@
                     getCurrentMethod(),
                     getCurrentCallerPosition());
             Position currentPosition = remapAndAdd(position, positionRemapper, mappedPositions);
+            positionEventEmitter.emitPositionEvents(getCurrentPc(), currentPosition);
             if (currentPosition != position) {
               inlinedOriginalPosition.set(true);
             }
-            positionEventEmitter.emitPositionEvents(getCurrentPc(), currentPosition);
             emittedPc = getCurrentPc();
           }
 
@@ -646,18 +651,74 @@
             debugInfo.parameters,
             processedEvents.toArray(DexDebugEvent.EMPTY_ARRAY));
 
-    // TODO(b/111253214) Remove this as soon as we have external tests testing not only the
-    // remapping but whether the non-positional debug events remain intact.
-    if (identityMapping && !inlinedOriginalPosition.get()) {
-      assert optimizedDebugInfo.startLine == debugInfo.startLine;
-      assert optimizedDebugInfo.events.length == debugInfo.events.length;
-      for (int i = 0; i < debugInfo.events.length; ++i) {
-        assert optimizedDebugInfo.events[i].equals(debugInfo.events[i]);
-      }
-    }
+    assert !identityMapping
+        || inlinedOriginalPosition.get()
+        || checkIdentityMapping(debugInfo, optimizedDebugInfo);
+
     dexCode.setDebugInfo(optimizedDebugInfo);
   }
 
+  private static void optimizeDexCodePositionsForPc(
+      DexEncodedMethod method,
+      PositionRemapper positionRemapper,
+      List<MappedPosition> mappedPositions) {
+    // Do the actual processing for each method.
+    DexCode dexCode = method.getCode().asDexCode();
+    DexDebugInfo debugInfo = dexCode.getDebugInfo();
+
+    Pair<Integer, Position> lastPosition = new Pair<>();
+
+    DexDebugEventVisitor visitor =
+        new DexDebugPositionState(debugInfo.startLine, method.method) {
+          @Override
+          public void visit(Default defaultEvent) {
+            super.visit(defaultEvent);
+            assert getCurrentLine() >= 0;
+            if (lastPosition.getSecond() != null) {
+              remapAndAddForPc(
+                  lastPosition.getFirst(),
+                  getCurrentPc(),
+                  lastPosition.getSecond(),
+                  positionRemapper,
+                  mappedPositions);
+            }
+            lastPosition.setFirst(getCurrentPc());
+            lastPosition.setSecond(
+                new Position(
+                    getCurrentLine(),
+                    getCurrentFile(),
+                    getCurrentMethod(),
+                    getCurrentCallerPosition()));
+          }
+        };
+
+    for (DexDebugEvent event : debugInfo.events) {
+      event.accept(visitor);
+    }
+
+    if (lastPosition.getSecond() != null) {
+      int lastPc = dexCode.instructions[dexCode.instructions.length - 1].getOffset();
+      remapAndAddForPc(
+          lastPosition.getFirst(),
+          lastPc + 1,
+          lastPosition.getSecond(),
+          positionRemapper,
+          mappedPositions);
+    }
+
+    dexCode.setDebugInfo(null);
+  }
+
+  private static boolean checkIdentityMapping(
+      DexDebugInfo originalDebugInfo, DexDebugInfo optimizedDebugInfo) {
+    assert optimizedDebugInfo.startLine == originalDebugInfo.startLine;
+    assert optimizedDebugInfo.events.length == originalDebugInfo.events.length;
+    for (int i = 0; i < originalDebugInfo.events.length; ++i) {
+      assert optimizedDebugInfo.events[i].equals(originalDebugInfo.events[i]);
+    }
+    return true;
+  }
+
   private static void optimizeCfCodePositions(
       DexEncodedMethod method,
       PositionRemapper positionRemapper,
@@ -667,8 +728,7 @@
     CfCode oldCode = method.getCode().asCfCode();
     List<CfInstruction> oldInstructions = oldCode.getInstructions();
     List<CfInstruction> newInstructions = new ArrayList<>(oldInstructions.size());
-    for (int i = 0; i < oldInstructions.size(); ++i) {
-      CfInstruction oldInstruction = oldInstructions.get(i);
+    for (CfInstruction oldInstruction : oldInstructions) {
       CfInstruction newInstruction;
       if (oldInstruction instanceof CfPosition) {
         CfPosition cfPosition = (CfPosition) oldInstruction;
@@ -702,4 +762,19 @@
             oldPosition.method, oldPosition.line, oldPosition.callerPosition, newPosition.line));
     return newPosition;
   }
+
+  private static void remapAndAddForPc(
+      int startPc,
+      int endPc,
+      Position position,
+      PositionRemapper remapper,
+      List<MappedPosition> mappedPositions) {
+    Pair<Position, Position> remappedPosition = remapper.createRemappedPosition(position);
+    Position oldPosition = remappedPosition.getFirst();
+    for (int currentPc = startPc; currentPc < endPc; currentPc++) {
+      mappedPositions.add(
+          new MappedPosition(
+              oldPosition.method, oldPosition.line, oldPosition.callerPosition, currentPc));
+    }
+  }
 }
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 5297ee9..64ce874 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -72,5 +72,3 @@
 -neverinline class * {
   @classmerging.NeverInline <methods>;
 }
-
--printmapping
diff --git a/src/test/examplesJava11/backport/CharSequenceBackportJava11Main.java b/src/test/examplesJava11/backport/CharSequenceBackportJava11Main.java
new file mode 100644
index 0000000..d0888f8
--- /dev/null
+++ b/src/test/examplesJava11/backport/CharSequenceBackportJava11Main.java
@@ -0,0 +1,46 @@
+package backport;
+
+public final class CharSequenceBackportJava11Main {
+  public static void main(String[] args) {
+    testCompare();
+  }
+
+  private static void testCompare() {
+    assertTrue(CharSequence.compare("Hello", "Hello") == 0);
+
+    assertTrue(CharSequence.compare("Hey", "Hello") > 0);
+    assertTrue(CharSequence.compare("Hello", "Hey") < 0);
+
+    assertTrue(CharSequence.compare("Hel", "Hello") < 0);
+    assertTrue(CharSequence.compare("Hello", "Hel") > 0);
+
+    assertTrue(CharSequence.compare("", "") == 0);
+    assertTrue(CharSequence.compare("", "Hello") < 0);
+    assertTrue(CharSequence.compare("Hello", "") > 0);
+
+    // Different CharSequence types:
+    assertTrue(CharSequence.compare("Hello", new StringBuilder("Hello")) == 0);
+    assertTrue(CharSequence.compare(new StringBuffer("hey"), "Hello") > 0);
+    assertTrue(CharSequence.compare(new StringBuffer("Hello"), new StringBuilder("Hey")) < 0);
+
+    try {
+      throw new AssertionError(CharSequence.compare(null, "Hello"));
+    } catch (NullPointerException expected) {
+    }
+    try {
+      throw new AssertionError(CharSequence.compare("Hello", null));
+    } catch (NullPointerException expected) {
+    }
+    try {
+      // Ensure a == b fast path does not happen before null checks.
+      throw new AssertionError(CharSequence.compare(null, null));
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  private static void assertTrue(boolean value) {
+    if (!value) {
+      throw new AssertionError("Expected <true> but was <false>");
+    }
+  }
+}
diff --git a/src/test/examplesJava11/backport/CharacterBackportJava11Main.java b/src/test/examplesJava11/backport/CharacterBackportJava11Main.java
index a580b5b..487c7b0 100644
--- a/src/test/examplesJava11/backport/CharacterBackportJava11Main.java
+++ b/src/test/examplesJava11/backport/CharacterBackportJava11Main.java
@@ -1,3 +1,7 @@
+// Copyright (c) 2020, 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 backport;
 
 public final class CharacterBackportJava11Main {
diff --git a/src/test/java/com/android/tools/r8/BackportedMethodListTest.java b/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
new file mode 100644
index 0000000..bb344d7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
@@ -0,0 +1,154 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BackportedMethodListTest {
+
+  @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  enum Mode {
+    NO_LIBRARY,
+    LIBRARY,
+    LIBRARY_DESUGAR
+  }
+
+  @Parameterized.Parameters(name = "Mode: {0}")
+  public static Object[] data() {
+    return Mode.values();
+  }
+
+  private final Mode mode;
+
+  public BackportedMethodListTest(Mode mode) {
+    this.mode = mode;
+  }
+
+  private static class ListStringConsumer implements StringConsumer {
+    List<String> strings = new ArrayList<>();
+    boolean finished = false;
+
+    @Override
+    public void accept(String string, DiagnosticsHandler handler) {
+      strings.add(string);
+    }
+
+    @Override
+    public void finished(DiagnosticsHandler handler) {
+      finished = true;
+    }
+  }
+
+  private void checkContent(int apiLevel, List<String> backports) {
+    // Java 8 methods added at various API levels.
+    assertEquals(
+        apiLevel < AndroidApiLevel.K.getLevel(), backports.contains("java/lang/Byte#compare(BB)I"));
+    assertEquals(
+        apiLevel < AndroidApiLevel.N.getLevel(),
+        backports.contains("java/lang/Integer#hashCode(I)I"));
+    assertEquals(
+        apiLevel < AndroidApiLevel.O.getLevel(),
+        backports.contains("java/lang/Short#toUnsignedLong(S)J"));
+
+    // Java 9, 10 and 11 Optional methods which require Android N or library desugaring.
+    assertEquals(
+        mode == Mode.LIBRARY_DESUGAR || apiLevel >= AndroidApiLevel.N.getLevel(),
+        backports.contains(
+            "java/util/Optional#or(Ljava/util/function/Supplier;)Ljava/util/Optional;"));
+    assertEquals(
+        mode == Mode.LIBRARY_DESUGAR || apiLevel >= AndroidApiLevel.N.getLevel(),
+        backports.contains("java/util/OptionalInt#orElseThrow()I"));
+    assertEquals(
+        mode == Mode.LIBRARY_DESUGAR || apiLevel >= AndroidApiLevel.N.getLevel(),
+        backports.contains("java/util/OptionalLong#isEmpty()Z"));
+
+    // Java 9, 10 and 11 methods.
+    assertTrue(backports.contains("java/lang/StrictMath#multiplyExact(JI)J"));
+    assertTrue(backports.contains("java/util/List#copyOf(Ljava/util/Collection;)Ljava/util/List;"));
+    assertTrue(backports.contains("java/lang/Character#toString(I)Ljava/lang/String;"));
+  }
+
+  private void addLibraryDesugaring(BackportedMethodListCommand.Builder builder) {
+    builder
+        .addDesugaredLibraryConfiguration(
+            StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P.getLevel()));
+  }
+
+  @Test
+  public void testConsumer() throws Exception {
+    for (int apiLevel = 1; apiLevel < AndroidApiLevel.LATEST.getLevel(); apiLevel++) {
+      ListStringConsumer consumer = new ListStringConsumer();
+      BackportedMethodListCommand.Builder builder =
+          BackportedMethodListCommand.builder().setMinApiLevel(apiLevel).setConsumer(consumer);
+      if (mode == Mode.LIBRARY) {
+        builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P.getLevel()));
+      } else if (mode == Mode.LIBRARY_DESUGAR) {
+        addLibraryDesugaring(builder);
+      }
+      BackportedMethodList.run(builder.build());
+      assertTrue(consumer.finished);
+      checkContent(apiLevel, consumer.strings);
+    }
+  }
+
+  @Test
+  public void testFile() throws Exception {
+    for (int apiLevel = 1; apiLevel < AndroidApiLevel.LATEST.getLevel(); apiLevel++) {
+      Path output = temp.newFile().toPath();
+      BackportedMethodListCommand.Builder builder =
+          BackportedMethodListCommand.builder().setMinApiLevel(apiLevel).setOutputPath(output);
+      if (mode == Mode.LIBRARY) {
+        builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P.getLevel()));
+      } else if (mode == Mode.LIBRARY_DESUGAR) {
+        addLibraryDesugaring(builder);
+      }
+      BackportedMethodList.run(builder.build());
+      checkContent(apiLevel, Files.readAllLines(output));
+    }
+  }
+
+  @Test
+  public void testFullList() throws Exception {
+    Assume.assumeTrue(mode == Mode.NO_LIBRARY);
+    ListStringConsumer consumer = new ListStringConsumer();
+    // Not setting neither min API level not library should produce the full list.
+    BackportedMethodList.run(BackportedMethodListCommand.builder().setConsumer(consumer).build());
+    assertTrue(consumer.finished);
+    checkContent(1, consumer.strings);
+  }
+
+  @Test
+  public void requireLibraryForDesugar() {
+    Assume.assumeTrue(mode == Mode.LIBRARY_DESUGAR);
+    // Require library when a desugar configuration is passed.
+    try {
+      BackportedMethodList.run(
+          BackportedMethodListCommand.builder()
+              .addDesugaredLibraryConfiguration(
+                  StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
+              .setConsumer(new ListStringConsumer())
+              .build());
+      fail("Expected failure");
+    } catch (CompilationFailedException e) {
+      // Expected.
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/DXTestBuilder.java b/src/test/java/com/android/tools/r8/DXTestBuilder.java
index 84ecee1..e621784 100644
--- a/src/test/java/com/android/tools/r8/DXTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/DXTestBuilder.java
@@ -53,7 +53,10 @@
       List<String> args = new ArrayList<>();
       args.add("--output=" + outJar.toString());
       args.addAll(injars.stream().map(Path::toString).collect(Collectors.toList()));
-      ProcessResult result = ToolHelper.runDX(args.toArray(StringUtils.EMPTY_ARRAY));
+      ProcessResult result =
+          ToolHelper.runProcess(
+              ToolHelper.createProcessBuilderForRunningDx(args.toArray(StringUtils.EMPTY_ARRAY)),
+              getStdoutForTesting());
       if (result.exitCode != 0) {
         throw new CompilationFailedException(result.toString());
       }
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
index 0ebe047..11e6692 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
@@ -137,7 +137,7 @@
       command.addAll(programJars.stream().map(Path::toString).collect(Collectors.toList()));
 
       ProcessBuilder processBuilder = new ProcessBuilder(command);
-      ProcessResult processResult = ToolHelper.runProcess(processBuilder);
+      ProcessResult processResult = ToolHelper.runProcess(processBuilder, getStdoutForTesting());
       assertEquals(processResult.stderr, 0, processResult.exitCode);
       String proguardMap =
           proguardMapFile.toFile().exists()
diff --git a/src/test/java/com/android/tools/r8/KotlinTestBase.java b/src/test/java/com/android/tools/r8/KotlinTestBase.java
index a470bb3..c3ce381 100644
--- a/src/test/java/com/android/tools/r8/KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/KotlinTestBase.java
@@ -6,8 +6,12 @@
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
+import java.io.IOException;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.List;
+import java.util.stream.Collectors;
 
 public abstract class KotlinTestBase extends TestBase {
 
@@ -28,6 +32,13 @@
     this.targetVersion = targetVersion;
   }
 
+  protected static List<Path> getKotlinFilesInTestPackage(Package pkg) throws IOException {
+    String folder = DescriptorUtils.getBinaryNameFromJavaType(pkg.getName());
+    return Files.walk(Paths.get(ToolHelper.TESTS_DIR, "java", folder))
+        .filter(path -> path.toString().endsWith(".kt"))
+        .collect(Collectors.toList());
+  }
+
   protected static Path getKotlinFileInTest(String folder, String fileName) {
     return Paths.get(ToolHelper.TESTS_DIR, "java", folder, fileName + FileUtils.KT_EXTENSION);
   }
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 9012ca4..9463a49 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -101,7 +101,7 @@
         command.add("-dontobfuscate");
       }
       ProcessBuilder pbuilder = new ProcessBuilder(command);
-      ProcessResult result = ToolHelper.runProcess(pbuilder);
+      ProcessResult result = ToolHelper.runProcess(pbuilder, getStdoutForTesting());
       if (result.exitCode != 0) {
         throw new CompilationFailedException(result.toString());
       }
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 39ac985..db80105 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -102,7 +102,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 119, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 118, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -110,7 +110,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = true)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 8, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
         .run();
   }
 
@@ -142,7 +142,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 119, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 118, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -150,7 +150,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = true)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 8, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
         .run();
   }
 
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index 50eb3be..2ec28b8 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -65,6 +65,13 @@
     return self();
   }
 
+  public <E extends Throwable> R8TestRunResult inspectOriginalStackTrace(
+      ThrowingBiConsumer<StackTrace, CodeInspector, E> consumer)
+      throws E, IOException, ExecutionException {
+    consumer.accept(getOriginalStackTrace(), new CodeInspector(app, proguardMap));
+    return self();
+  }
+
   public GraphInspector graphInspector() throws IOException, ExecutionException {
     assertSuccess();
     return graphInspector.get();
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index c1a612f..5094ec5 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -33,6 +33,7 @@
 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.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.SmaliWriter;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.origin.Origin;
@@ -72,7 +73,6 @@
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.common.io.ByteStreams;
@@ -82,7 +82,6 @@
 import java.io.IOException;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -236,6 +235,10 @@
     return ClassFileTransformer.create(clazz);
   }
 
+  public static ClassFileTransformer transformer(byte[] clazz) {
+    return ClassFileTransformer.create(clazz);
+  }
+
   // Actually running Proguard should only be during development.
   private static final boolean RUN_PROGUARD = System.getProperty("run_proguard") != null;
   // Actually running r8.jar in a forked process.
@@ -437,12 +440,12 @@
   protected static AndroidApp.Builder buildClasses(
       Collection<Class<?>> programClasses, Collection<Class<?>> libraryClasses) throws IOException {
     AndroidApp.Builder builder = AndroidApp.builder();
-    for (Class clazz : programClasses) {
+    for (Class<?> clazz : programClasses) {
       builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
     }
     if (!libraryClasses.isEmpty()) {
       PreloadedClassFileProvider.Builder libraryBuilder = PreloadedClassFileProvider.builder();
-      for (Class clazz : libraryClasses) {
+      for (Class<?> clazz : libraryClasses) {
         Path file = ToolHelper.getClassFileForTestClass(clazz);
         libraryBuilder.addResource(DescriptorUtils.javaTypeToDescriptor(clazz.getCanonicalName()),
             Files.readAllBytes(file));
@@ -455,7 +458,7 @@
   protected static AndroidApp readClassesAndRuntimeJar(
       List<Class<?>> programClasses, Backend backend) throws IOException {
     AndroidApp.Builder builder = AndroidApp.builder();
-    for (Class clazz : programClasses) {
+    for (Class<?> clazz : programClasses) {
       builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
     }
     if (backend == Backend.DEX) {
@@ -477,7 +480,7 @@
    * Copy test classes to the specified directory.
    */
   protected void copyTestClasses(Path dest, Class... classes) throws IOException {
-    for (Class clazz : classes) {
+    for (Class<?> clazz : classes) {
       Path path = dest.resolve(clazz.getCanonicalName().replace('.', '/') + ".class");
       Files.createDirectories(path.getParent());
       Files.copy(ToolHelper.getClassFileForTestClass(clazz), path);
@@ -510,7 +513,7 @@
   /** Create a temporary JAR file containing the specified test classes. */
   protected void addTestClassesToJar(JarOutputStream out, Iterable<Class<?>> classes)
       throws IOException {
-    for (Class clazz : classes) {
+    for (Class<?> clazz : classes) {
       try (FileInputStream in =
           new FileInputStream(ToolHelper.getClassFileForTestClass(clazz).toFile())) {
         out.putNextEntry(new ZipEntry(ToolHelper.getJarEntryForTestClass(clazz)));
@@ -587,7 +590,8 @@
       throws Exception {
     Timing timing = Timing.empty();
     InternalOptions options = new InternalOptions();
-    DexApplication application = new ApplicationReader(app, options, timing).read().toDirect();
+    DirectMappedDexApplication application =
+        new ApplicationReader(app, options, timing).read().toDirect();
     AppView<AppInfoWithSubtyping> appView =
         AppView.createForR8(new AppInfoWithSubtyping(application), options);
     appView.setAppServices(AppServices.builder(appView).build());
@@ -645,7 +649,7 @@
   }
 
   protected static DexMethod buildNullaryVoidMethod(
-      Class clazz, String name, DexItemFactory factory) {
+      Class<?> clazz, String name, DexItemFactory factory) {
     return buildMethod(
         Reference.method(Reference.classFromClass(clazz), name, Collections.emptyList(), null),
         factory);
@@ -659,7 +663,7 @@
   }
 
   private static List<ProguardConfigurationRule> buildKeepRuleForClass(
-      Class clazz, DexItemFactory factory) {
+      Class<?> clazz, DexItemFactory factory) {
     Builder keepRuleBuilder = ProguardKeepRule.builder();
     keepRuleBuilder.setSource("buildKeepRuleForClass " + clazz.getTypeName());
     keepRuleBuilder.setType(ProguardKeepRuleType.KEEP);
@@ -671,7 +675,7 @@
   }
 
   private static List<ProguardConfigurationRule> buildKeepRuleForClassAndMethods(
-      Class clazz, DexItemFactory factory) {
+      Class<?> clazz, DexItemFactory factory) {
     Builder keepRuleBuilder = ProguardKeepRule.builder();
     keepRuleBuilder.setSource("buildKeepRuleForClass " + clazz.getTypeName());
     keepRuleBuilder.setType(ProguardKeepRuleType.KEEP);
@@ -735,20 +739,6 @@
     return jarTestClasses(classes.toArray(new Class<?>[]{}));
   }
 
-  /**
-   * Get the class name generated by javac.
-   */
-  protected static String getJavacGeneratedClassName(Class clazz) {
-    List<String> parts = Lists.newArrayList(clazz.getCanonicalName().split("\\."));
-    Class enclosing = clazz;
-    while (enclosing.getEnclosingClass() != null) {
-      parts.set(parts.size() - 2, parts.get(parts.size() - 2) + "$" + parts.get(parts.size() - 1));
-      parts.remove(parts.size() - 1);
-      enclosing = clazz.getEnclosingClass();
-    }
-    return String.join(".", parts);
-  }
-
   protected static List<Object[]> buildParameters(Object... arraysOrIterables) {
     Function<Object, List<Object>> arrayOrIterableToList =
         arrayOrIterable -> {
@@ -897,23 +887,16 @@
    * Generate a Proguard configuration for keeping the "static void main(String[])" method of the
    * specified class.
    */
-  public static String keepMainProguardConfiguration(Class clazz) {
-    return keepMainProguardConfiguration(clazz, ImmutableList.of());
+  public static String keepMainProguardConfiguration(Class<?> clazz) {
+    return keepMainProguardConfiguration(clazz.getTypeName());
   }
 
   /**
    * Generate a Proguard configuration for keeping the "static void main(String[])" method of the
    * specified class.
    */
-  public static String keepMainProguardConfiguration(Class clazz, List<String> additionalLines) {
-    String modifier = (clazz.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC ? "public " : "";
-    return String.join(System.lineSeparator(),
-        Iterables.concat(ImmutableList.of(
-            "-keep " + modifier + "class " + getJavacGeneratedClassName(clazz) + " {",
-            "  public static void main(java.lang.String[]);",
-            "}",
-            "-printmapping"),
-            additionalLines));
+  public static String keepMainProguardConfiguration(Class<?> clazz, List<String> additionalLines) {
+    return keepMainProguardConfiguration(clazz.getTypeName()) + StringUtils.lines(additionalLines);
   }
 
   /**
@@ -923,15 +906,12 @@
    * The class is assumed to be public.
    */
   public static String keepMainProguardConfiguration(String clazz) {
-    return "-keep public class " + clazz + " {\n"
-        + "  public static void main(java.lang.String[]);\n"
-        + "}\n"
-        + "-printmapping\n";
+    return StringUtils.lines(
+        "-keep class " + clazz + " {", "  public static void main(java.lang.String[]);", "}");
   }
 
   public static String noShrinkingNoMinificationProguardConfiguration() {
-    return "-dontshrink\n"
-        + "-dontobfuscate\n";
+    return StringUtils.lines("-dontshrink", "-dontobfuscate");
   }
 
   /**
@@ -939,7 +919,7 @@
    * specified class and specify if -allowaccessmodification and -dontobfuscate are added as well.
    */
   public static String keepMainProguardConfiguration(
-      Class clazz, boolean allowaccessmodification, boolean obfuscate) {
+      Class<?> clazz, boolean allowaccessmodification, boolean obfuscate) {
     return keepMainProguardConfiguration(clazz)
         + (allowaccessmodification ? "-allowaccessmodification\n" : "")
         + (obfuscate ? "-printmapping\n" : "-dontobfuscate\n");
@@ -956,7 +936,7 @@
    * Generate a Proguard configuration for keeping the "static void main(String[])" method of the
    * specified class and add rules to inline methods with the inlining annotation.
    */
-  public static String keepMainProguardConfigurationWithInliningAnnotation(Class clazz) {
+  public static String keepMainProguardConfigurationWithInliningAnnotation(Class<?> clazz) {
     return "-forceinline class * { @com.android.tools.r8.ForceInline *; }"
         + System.lineSeparator()
         + "-neverinline class * { @com.android.tools.r8.NeverInline *; }"
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 628e9c5..5f0843f 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -3,6 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
@@ -12,6 +16,7 @@
 import com.android.tools.r8.utils.AndroidAppConsumers;
 import com.android.tools.r8.utils.ForwardingOutputStream;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThrowingOutputStream;
 import com.google.common.base.Suppliers;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -52,7 +57,9 @@
   private AndroidApiLevel defaultMinApiLevel = ToolHelper.getMinApiLevelForDexVm();
   private Consumer<InternalOptions> optionsConsumer = DEFAULT_OPTIONS;
   private ByteArrayOutputStream stdout = null;
+  private PrintStream oldStdout = null;
   private ByteArrayOutputStream stderr = null;
+  private PrintStream oldStderr = null;
   protected OutputMode outputMode = OutputMode.DexIndexed;
 
   TestCompilerBuilder(TestState state, B builder, Backend backend) {
@@ -95,34 +102,43 @@
         builder.addLibraryFiles(TestBase.runtimeJar(backend));
       }
     }
-    PrintStream oldOut = System.out;
-    PrintStream oldErr = System.err;
-    CR cr = null;
+    assertNull(oldStdout);
+    oldStdout = System.out;
+    assertNull(oldStderr);
+    oldStderr = System.err;
+    CR cr;
     try {
       if (stdout != null) {
+        assertTrue(allowStdoutMessages);
         System.setOut(new PrintStream(new ForwardingOutputStream(stdout, System.out)));
+      } else if (!allowStdoutMessages) {
+        System.setOut(
+            new PrintStream(
+                new ThrowingOutputStream<>(
+                    () -> new AssertionError("Unexpected print to stdout"))));
       }
       if (stderr != null) {
+        assertTrue(allowStderrMessages);
         System.setErr(new PrintStream(new ForwardingOutputStream(stderr, System.err)));
+      } else if (!allowStderrMessages) {
+        System.setErr(
+            new PrintStream(
+                new ThrowingOutputStream<>(
+                    () -> new AssertionError("Unexpected print to stderr"))));
       }
-      cr = internalCompile(builder, optionsConsumer, Suppliers.memoize(sink::build));
-      cr.addRunClasspathFiles(additionalRunClassPath);
+      cr =
+          internalCompile(builder, optionsConsumer, Suppliers.memoize(sink::build))
+              .addRunClasspathFiles(additionalRunClassPath);
       return cr;
     } finally {
       if (stdout != null) {
         getState().setStdout(stdout.toString());
-        System.setOut(oldOut);
-        if (cr != null && !allowStdoutMessages) {
-          cr.assertNoStdout();
-        }
       }
+      System.setOut(oldStdout);
       if (stderr != null) {
         getState().setStderr(stderr.toString());
-        System.setErr(oldErr);
-        if (cr != null && !allowStderrMessages) {
-          cr.assertNoStderr();
-        }
       }
+      System.setErr(oldStderr);
     }
   }
 
@@ -319,6 +335,16 @@
     return allowStdoutMessages();
   }
 
+  /**
+   * If {@link #allowStdoutMessages} is false, then {@link System#out} will be replaced temporarily
+   * by a {@link ThrowingOutputStream}. To allow the testing infrastructure to print messages to the
+   * terminal, this method provides a reference to the original {@link System#out}.
+   */
+  public PrintStream getStdoutForTesting() {
+    assertNotNull(oldStdout);
+    return oldStdout;
+  }
+
   public T allowStderrMessages() {
     allowStdoutMessages = true;
     return self();
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index bbc9d4e..46bf0a1 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -17,6 +17,8 @@
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
 import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -112,6 +114,10 @@
   }
 
   public RR assertSuccessWithOutputLines(String... expected) {
+    return assertSuccessWithOutputLines(Arrays.asList(expected));
+  }
+
+  public RR assertSuccessWithOutputLines(List<String> expected) {
     return assertSuccessWithOutput(StringUtils.lines(expected));
   }
 
@@ -129,14 +135,10 @@
     return new CodeInspector(app);
   }
 
-  public RR inspect(ThrowingConsumer<CodeInspector, NoSuchMethodException> consumer)
-      throws IOException, ExecutionException {
+  public <E extends Throwable> RR inspect(ThrowingConsumer<CodeInspector, E> consumer)
+      throws IOException, ExecutionException, E {
     CodeInspector inspector = inspector();
-    try {
-      consumer.accept(inspector);
-    } catch (NoSuchMethodException exception) {
-      throw new RuntimeException(exception);
-    }
+    consumer.accept(inspector);
     return self();
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 2defd19..13afb51 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -205,6 +205,10 @@
     return addKeepRules("-keepattributes " + String.join(",", attributes));
   }
 
+  public T addKeepAttributeLineNumberTable() {
+    return addKeepRules("-keepattributes LineNumberTable");
+  }
+
   public T addKeepAllAttributes() {
     return addKeepAttributes("*");
   }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index effe9d9..f9c1086 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.graph.AssemblyWriter;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
@@ -66,6 +67,7 @@
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -780,6 +782,18 @@
     return path;
   }
 
+  public static Path getMostRecentAndroidJar() {
+    List<AndroidApiLevel> apiLevels = AndroidApiLevel.getAndroidApiLevelsSorted();
+    ListIterator<AndroidApiLevel> iterator = apiLevels.listIterator(apiLevels.size());
+    while (iterator.hasPrevious()) {
+      AndroidApiLevel apiLevel = iterator.previous();
+      if (hasAndroidJar(apiLevel)) {
+        return getAndroidJar(apiLevel);
+      }
+    }
+    throw new Unreachable("Unable to find a most recent android.jar");
+  }
+
   public static Path getKotlinStdlibJar() {
     Path path = Paths.get(KT_STDLIB);
     assert Files.exists(path) : "Expected kotlin stdlib jar";
@@ -910,7 +924,7 @@
       case V9_0_0:
         return AndroidApiLevel.P;
       case V8_1_0:
-        return AndroidApiLevel.O;
+        return AndroidApiLevel.O_MR1;
       case V7_0_0:
         return AndroidApiLevel.N;
       case V6_0_1:
@@ -1053,14 +1067,13 @@
     return String.join("/", parts);
   }
 
-  public static DexApplication buildApplication(List<String> fileNames)
+  public static DirectMappedDexApplication buildApplication(List<String> fileNames)
       throws IOException, ExecutionException {
     return buildApplicationWithAndroidJar(fileNames, getDefaultAndroidJar());
   }
 
-  public static DexApplication buildApplicationWithAndroidJar(
-      List<String> fileNames, Path androidJar)
-      throws IOException, ExecutionException {
+  public static DirectMappedDexApplication buildApplicationWithAndroidJar(
+      List<String> fileNames, Path androidJar) throws IOException, ExecutionException {
     AndroidApp input =
         AndroidApp.builder()
             .addProgramFiles(ListUtils.map(fileNames, Paths::get))
@@ -1244,6 +1257,15 @@
   }
 
   public static ProcessResult runDX(Path workingDirectory, String... args) throws IOException {
+    return runProcess(createProcessBuilderForRunningDx(workingDirectory, args));
+  }
+
+  public static ProcessBuilder createProcessBuilderForRunningDx(String... args) {
+    return createProcessBuilderForRunningDx(null, args);
+  }
+
+  public static ProcessBuilder createProcessBuilderForRunningDx(
+      Path workingDirectory, String... args) {
     Assume.assumeTrue(ToolHelper.artSupported());
     DXCommandBuilder builder = new DXCommandBuilder();
     for (String arg : args) {
@@ -1253,7 +1275,7 @@
     if (workingDirectory != null) {
       pb.directory(workingDirectory.toFile());
     }
-    return runProcess(pb);
+    return pb;
   }
 
   public static ProcessResult runJava(Class clazz) throws Exception {
@@ -1968,8 +1990,13 @@
   }
 
   public static ProcessResult runProcess(ProcessBuilder builder) throws IOException {
+    return runProcess(builder, System.out);
+  }
+
+  public static ProcessResult runProcess(ProcessBuilder builder, PrintStream out)
+      throws IOException {
     String command = String.join(" ", builder.command());
-    System.out.println(command);
+    out.println(command);
     return drainProcessOutputStreams(builder.start(), command);
   }
 
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
index c37c71b..6bc3814 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
@@ -5,67 +5,65 @@
 package com.android.tools.r8.bridgeremoval;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.bridgeremoval.bridgestoremove.Main;
-import com.android.tools.r8.bridgeremoval.bridgestoremove.Outer;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
-import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
-import java.lang.reflect.Method;
-import java.nio.file.Path;
 import java.util.Collections;
 import java.util.List;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class RemoveVisibilityBridgeMethodsTest extends TestBase {
 
-  private void run(boolean obfuscate) throws Exception {
-    List<Class<?>> classes = ImmutableList.of(Outer.class, Main.class);
-    String proguardConfig = keepMainProguardConfiguration(Main.class, true, obfuscate);
-    CodeInspector inspector = new CodeInspector(compileWithR8(classes, proguardConfig));
+  private final boolean minification;
+  private final TestParameters parameters;
 
-    List<Method> removedMethods = ImmutableList.of(
-        Outer.SubClass.class.getMethod("method"),
-        Outer.StaticSubClass.class.getMethod("method"));
+  @Parameterized.Parameters(name = "{1}, minification: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
 
-    removedMethods.forEach(method -> assertFalse(inspector.method(method).isPresent()));
+  public RemoveVisibilityBridgeMethodsTest(boolean minification, TestParameters parameters) {
+    this.minification = minification;
+    this.parameters = parameters;
   }
 
   @Test
-  public void testWithObfuscation() throws Exception {
-    run(true);
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(RemoveVisibilityBridgeMethodsTest.class)
+        .addKeepMainRule(Main.class)
+        .allowAccessModification()
+        .minification(minification)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccess();
   }
 
-  @Test
-  public void testWithoutObfuscation() throws Exception {
-    run(false);
-  }
-
-  @Test
-  public void regressionTest_b76383728_WithObfuscation() throws Exception {
-    runRegressionTest_b76383728(true);
-  }
-
-  @Test
-  public void regressionTest_b76383728_WithoutObfuscation() throws Exception {
-    runRegressionTest_b76383728(false);
+  private void inspect(CodeInspector inspector) throws Exception {
+    assertThat(inspector.method(Outer.SubClass.class.getMethod("method")), not(isPresent()));
+    assertThat(inspector.method(Outer.StaticSubClass.class.getMethod("method")), not(isPresent()));
   }
 
   /**
    * Regression test for b76383728 to make sure we correctly identify and remove real visibility
    * forward bridge methods synthesized by javac.
    */
-  private void runRegressionTest_b76383728(boolean obfuscate) throws Exception {
+  @Test
+  public void regressionTest_b76383728() throws Exception {
     JasminBuilder jasminBuilder = new JasminBuilder();
 
     ClassBuilder superClass = jasminBuilder.addClass("SuperClass");
@@ -92,39 +90,71 @@
         "dup",
         "invokespecial " + subclass.name + "/<init>()V",
         "invokevirtual " + subclass.name + "/getMethod()Ljava/lang/String;",
-        "invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
-        "return"
-    );
+        "invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V",
+        "return");
 
-    final String mainClassName = mainClass.name;
-
-    String proguardConfig = keepMainProguardConfiguration(mainClass.name, true, obfuscate);
+    List<byte[]> programClassFileData = jasminBuilder.buildClasses();
 
     // Run input program on java.
-    Path outputDirectory = temp.newFolder().toPath();
-    jasminBuilder.writeClassFiles(outputDirectory);
-    ProcessResult javaResult = ToolHelper.runJava(outputDirectory, mainClassName);
-    assertEquals(0, javaResult.exitCode);
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClassFileData(programClassFileData)
+          .run(parameters.getRuntime(), mainClass.name)
+          .assertSuccessWithOutputLines("Hello World");
+    }
 
-    AndroidApp optimizedApp = compileWithR8(jasminBuilder.build(), proguardConfig,
-        // Disable inlining to avoid the (short) tested method from being inlined and then removed.
-        internalOptions -> internalOptions.enableInlining = false);
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(programClassFileData)
+        .addKeepMainRule(mainClass.name)
+        .addOptionsModification(options -> options.enableInlining = false)
+        .allowAccessModification()
+        .minification(minification)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject classSubject = inspector.clazz(superClass.name);
+              assertThat(classSubject, isPresent());
+              MethodSubject methodSubject =
+                  classSubject.method("java.lang.String", "method", Collections.emptyList());
+              assertThat(methodSubject, isPresent());
 
-    // Run optimized (output) program on ART
-    String artResult = runOnArt(optimizedApp, mainClassName);
-    assertEquals(javaResult.stdout, artResult);
+              classSubject = inspector.clazz(subclass.name);
+              assertThat(classSubject, isPresent());
+              methodSubject =
+                  classSubject.method("java.lang.String", "getMethod", Collections.emptyList());
+              assertThat(methodSubject, isPresent());
+            })
+        .run(parameters.getRuntime(), mainClass.name)
+        .assertSuccessWithOutputLines("Hello World");
+  }
 
-    CodeInspector inspector = new CodeInspector(optimizedApp);
+  static class Main {
 
-    ClassSubject classSubject = inspector.clazz(superClass.name);
-    assertThat(classSubject, isPresent());
-    MethodSubject methodSubject = classSubject
-        .method("java.lang.String", "method", Collections.emptyList());
-    assertThat(methodSubject, isPresent());
+    public static void main(String[] args) {
+      new Outer().create().method();
+      new Outer.StaticSubClass().method();
+    }
+  }
 
-    classSubject = inspector.clazz(subclass.name);
-    assertThat(classSubject, isPresent());
-    methodSubject = classSubject.method("java.lang.String", "getMethod", Collections.emptyList());
-    assertThat(methodSubject, isPresent());
+  static class Outer {
+
+    class SuperClass {
+      public void method() {}
+    }
+
+    // As SuperClass is package private SubClass will have a bridge method for "method".
+    public class SubClass extends SuperClass {}
+
+    public SubClass create() {
+      return new SubClass();
+    }
+
+    static class StaticSuperClass {
+      public void method() {}
+    }
+
+    // As SuperClass is package private SubClass will have a bridge method for "method".
+    public static class StaticSubClass extends StaticSuperClass {}
   }
 }
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/Main.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/Main.java
index 4c8ac31..28ad6b9 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/Main.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/Main.java
@@ -7,7 +7,7 @@
 public class Main {
 
   public static void main(String[] args) {
-    (new Outer()).create().method();
-    (new Outer.StaticSubClass()).method();
+    new Outer().create().method();
+    new Outer.StaticSubClass().method();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/cf/InlineCmpDoubleTest.java b/src/test/java/com/android/tools/r8/cf/InlineCmpDoubleTest.java
index b81d457..16dfaa7 100644
--- a/src/test/java/com/android/tools/r8/cf/InlineCmpDoubleTest.java
+++ b/src/test/java/com/android/tools/r8/cf/InlineCmpDoubleTest.java
@@ -1,23 +1,77 @@
 // Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-
 package com.android.tools.r8.cf;
 
-public class InlineCmpDoubleTest {
-  public static void main(String[] args) {
-   inlinee(42);
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+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 InlineCmpDoubleTest extends TestBase {
+
+  private final boolean enableInlining;
+  private final TestParameters parameters;
+
+  public InlineCmpDoubleTest(boolean enableInlining, TestParameters parameters) {
+    this.enableInlining = enableInlining;
+    this.parameters = parameters;
   }
 
-  public static void inlinee(int x) {
-    inlineMe(x + 41);
+  @Parameters(name = "{1}, inlining: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
-  public static int inlineMe(int x) {
-    // Side effect to ensure that the invocation is not removed simply because the method does not
-    // have any side effects.
-    System.out.println("In InlineCmpDoubleTest.inlineMe()");
-    double a = x / 255.0;
-    return a < 64.0 ? 42 : 43;
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(options -> options.enableInlining = enableInlining)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccess();
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(TestClass.class);
+    MethodSubject method =
+        clazz.method(new MethodSignature("inlineMe", "int", ImmutableList.of("int")));
+    assertEquals(enableInlining, !method.isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      inlinee(42);
+    }
+
+    public static void inlinee(int x) {
+      inlineMe(x + 41);
+    }
+
+    public static int inlineMe(int x) {
+      // Side effect to ensure that the invocation is not removed simply because the method does not
+      // have any side effects.
+      System.out.println("In InlineCmpDoubleTest.inlineMe()");
+      double a = x / 255.0;
+      return a < 64.0 ? 42 : 43;
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/cf/InlineCmpDoubleTestRunner.java b/src/test/java/com/android/tools/r8/cf/InlineCmpDoubleTestRunner.java
deleted file mode 100644
index dbcf557..0000000
--- a/src/test/java/com/android/tools/r8/cf/InlineCmpDoubleTestRunner.java
+++ /dev/null
@@ -1,75 +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.cf;
-
-import static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.AndroidAppConsumers;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
-import java.nio.file.Path;
-import java.util.List;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class InlineCmpDoubleTestRunner {
-
-  private static final Class CLASS = InlineCmpDoubleTest.class;
-
-  private final boolean enableInlining;
-
-  @Rule
-  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
-
-  public InlineCmpDoubleTestRunner(boolean enableInlining) {
-    this.enableInlining = enableInlining;
-  }
-
-  @Parameters(name = "inlining={0}")
-  public static Boolean[] data() {
-    return new Boolean[]{true, false};
-  }
-
-  @Test
-  public void test() throws Exception {
-    byte[] inputClass = ToolHelper.getClassAsBytes(CLASS);
-    AndroidAppConsumers appBuilder = new AndroidAppConsumers();
-    Path outPath = temp.getRoot().toPath().resolve("out.jar");
-    List<String> proguardKeepMain = ImmutableList.of(TestBase.keepMainProguardConfiguration(CLASS));
-    R8Command command =  R8Command.builder()
-        .setMode(CompilationMode.RELEASE)
-        .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
-        .setProgramConsumer(appBuilder.wrapClassFileConsumer(new ArchiveConsumer(outPath)))
-        .addProguardConfiguration(proguardKeepMain, Origin.unknown())
-        .addClassProgramData(inputClass, Origin.unknown())
-        .build();
-
-    AndroidApp app = ToolHelper.runR8(command, options -> {
-          options.enableInlining = enableInlining;
-    });
-
-    assertEquals(0, ToolHelper.runJava(outPath, CLASS.getCanonicalName()).exitCode);
-
-    CodeInspector inspector = new CodeInspector(app);
-    ClassSubject clazz = inspector.clazz(CLASS);
-    MethodSubject method =
-        clazz.method(new MethodSignature("inlineMe", "int", ImmutableList.of("int")));
-    assertEquals(enableInlining, !method.isPresent());
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTest.java b/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTest.java
index 4dc81f4..1415b49 100644
--- a/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTest.java
+++ b/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTest.java
@@ -6,6 +6,7 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
@@ -20,22 +21,30 @@
     Method runMethod = o.getClass().getMethod("run");
     runMethod.invoke(o);
   }
+
+  static Serializable getLambda() {
+    return (Runnable & Serializable) () -> System.out.println("base lambda");
+  }
 }
 
 class KeepDeserializeLambdaMethodTestDex extends KeepDeserializeLambdaMethodTest {
   public static void main(String[] args) throws Exception {
-    Serializable myLambda =
+    invokeLambda(getLambda());
+    invokeLambda(
         (Runnable & Serializable)
-            () -> System.out.println(KeepDeserializeLambdaMethodTest.LAMBDA_MESSAGE);
-    invokeLambda(myLambda);
+            () -> System.out.println(KeepDeserializeLambdaMethodTest.LAMBDA_MESSAGE));
   }
 }
 
 class KeepDeserializeLambdaMethodTestCf extends KeepDeserializeLambdaMethodTest {
 
   public static void main(String[] args) throws Exception {
-    Serializable myLambda = (Runnable & Serializable) () -> System.out.println(LAMBDA_MESSAGE);
+    invokeLambda(roundtrip(getLambda()));
+    invokeLambda(roundtrip((Runnable & Serializable) () -> System.out.println(LAMBDA_MESSAGE)));
+  }
 
+  private static Object roundtrip(Serializable myLambda)
+      throws IOException, ClassNotFoundException {
     byte[] bytes;
     {
       ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
@@ -51,10 +60,6 @@
       o = in.readObject();
       in.close();
     }
-    String name = o.getClass().getName();
-    if (!name.contains("KeepDeserializeLambdaMethodTestCf")) {
-      throw new RuntimeException("Unexpected class name " + name);
-    }
-    invokeLambda(o);
+    return o;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java b/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java
index 9c5d05d..8bb8e36 100644
--- a/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java
@@ -4,19 +4,19 @@
 
 package com.android.tools.r8.cf;
 
-import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.R8CompatTestBuilder;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
 import java.io.IOException;
+import java.util.List;
 import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -31,9 +31,12 @@
   private static final Class TEST_CLASS_DEX =
       com.android.tools.r8.cf.KeepDeserializeLambdaMethodTestDex.class;
 
+  private static final String EXPECTED =
+      StringUtils.lines("base lambda", KeepDeserializeLambdaMethodTest.LAMBDA_MESSAGE);
+
   @Parameters(name = "{0}")
   public static TestParametersCollection params() {
-    return getTestParameters().withCfRuntimes().withDexRuntime(Version.last()).build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -42,48 +45,66 @@
     this.parameters = parameters;
   }
 
+  private Class<?> getMainClass() {
+    return parameters.isCfRuntime() ? TEST_CLASS_CF : TEST_CLASS_DEX;
+  }
+
+  private List<Class<?>> getClasses() {
+    return ImmutableList.of(KeepDeserializeLambdaMethodTest.class, getMainClass());
+  }
+
   @Test
-  public void testNoTreeShakingCf() throws Exception {
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getClasses())
+        .run(parameters.getRuntime(), getMainClass())
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkPresenceOfDeserializedLambdas);
+  }
+
+  @Test
+  public void testDontKeepDeserializeLambdaR8() throws Exception {
     test(false);
   }
 
   @Test
-  public void testKeepRuleCf() throws Exception {
-    // Only run for CF runtimes, since the keep rule does not match anything when compiling for the
-    // DEX.
-    assumeTrue(parameters.isCfRuntime());
+  public void testKeepDeserializedLambdaR8() throws Exception {
     test(true);
   }
 
-  private void test(boolean keepRule)
+  private void test(boolean addKeepDeserializedLambdaRule)
       throws IOException, CompilationFailedException, ExecutionException {
-    Class testClass = parameters.isCfRuntime() ? TEST_CLASS_CF : TEST_CLASS_DEX;
     R8CompatTestBuilder builder =
         testForR8Compat(parameters.getBackend())
-            .addProgramClasses(
-                com.android.tools.r8.cf.KeepDeserializeLambdaMethodTest.class, testClass)
-            .setMinApi(parameters.getRuntime())
-            .addKeepMainRule(testClass)
-            .noMinification();
-    if (keepRule) {
+            .addProgramClasses(getClasses())
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(getMainClass());
+    if (addKeepDeserializedLambdaRule) {
+      builder.allowUnusedProguardConfigurationRules(parameters.isDexRuntime());
       builder.addKeepRules(
           "-keepclassmembers class * {",
           "private static synthetic java.lang.Object "
               + "$deserializeLambda$(java.lang.invoke.SerializedLambda);",
-          "}");
+          "}",
+          // TODO(b/148836254): Support deserialized lambdas without the need of additional rules.
+          "-keep class * { private static synthetic void lambda$*(); }");
     } else {
+      builder.noMinification();
       builder.noTreeShaking();
     }
-    R8TestRunResult result =
-        builder
-            .run(parameters.getRuntime(), testClass)
-            .assertSuccessWithOutputThatMatches(
-                containsString(KeepDeserializeLambdaMethodTest.LAMBDA_MESSAGE))
-            .inspect(
-                inspector -> {
-                  MethodSubject method =
-                      inspector.clazz(testClass).uniqueMethodWithName("$deserializeLambda$");
-                  assertEquals(parameters.isCfRuntime(), method.isPresent());
-                });
+    builder
+        .run(parameters.getRuntime(), getMainClass())
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkPresenceOfDeserializedLambdas);
+  }
+
+  private void checkPresenceOfDeserializedLambdas(CodeInspector inspector) {
+    for (Class<?> clazz : getClasses()) {
+      MethodSubject method = inspector.clazz(clazz).uniqueMethodWithName("$deserializeLambda$");
+      assertEquals(
+          "Unexpected status for $deserializedLambda$ on " + clazz.getSimpleName(),
+          parameters.isCfRuntime(),
+          method.isPresent());
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
index 3d55195..26e5a16 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -98,7 +98,6 @@
   private void configure(InternalOptions options) {
     options.enableSideEffectAnalysis = false;
     options.enableUnusedInterfaceRemoval = false;
-    options.testing.nondeterministicCycleElimination = true;
   }
 
   private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
@@ -236,12 +235,6 @@
   }
 
   // This test has a cycle in the call graph consisting of the methods A.<init> and B.<init>.
-  // When nondeterministicCycleElimination is enabled, we shuffle the nodes in the call graph
-  // before the cycle elimination. Therefore, it is nondeterministic if the cycle detector will
-  // detect the cycle upon the call edge A.<init> to B.<init>, or B.<init> to A.<init>. In order
-  // increase the likelihood that this test encounters both orderings, the test is repeated 5 times.
-  // Assuming that the chance of observing one of the two orderings is 50%, this test therefore has
-  // approximately 3% chance of succeeding although there is an issue.
   @Test
   public void testCallGraphCycle() throws Throwable {
     String main = "classmerging.CallGraphCycleTest";
diff --git a/src/test/java/com/android/tools/r8/debug/Regress148661132Test.java b/src/test/java/com/android/tools/r8/debug/Regress148661132Test.java
new file mode 100644
index 0000000..ae64de7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/Regress148661132Test.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2020, 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.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class Regress148661132Test extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public Regress148661132Test(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForD8().setMinApi(AndroidApiLevel.B).addProgramClassFileData(FlafKtDump.dump()).compile();
+  }
+
+  static class FlafKtDump implements Opcodes {
+
+    public static byte[] dump() {
+
+      ClassWriter cw = new ClassWriter(0);
+      cw.visit(V1_6, ACC_PUBLIC | ACC_FINAL | ACC_SUPER, "FlafKt", null, "java/lang/Object", null);
+      cw.visitSource("Flaf.kt", null);
+      cw.visitInnerClass("FlafKt$inlineFun$1", null, null, ACC_PUBLIC | ACC_FINAL | ACC_STATIC);
+
+      MethodVisitor mv =
+          cw.visitMethod(
+              ACC_PUBLIC | ACC_STATIC | ACC_SYNTHETIC,
+              "inlineFun$default",
+              "(LA;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)LA;",
+              null,
+              null);
+      mv.visitCode();
+      mv.visitVarInsn(ILOAD, 2);
+      mv.visitInsn(ICONST_2);
+      mv.visitInsn(IAND);
+      Label label0 = new Label();
+      mv.visitJumpInsn(IFEQ, label0);
+      mv.visitTypeInsn(NEW, "FlafKt$inlineFun$1");
+      mv.visitInsn(DUP);
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKESPECIAL, "FlafKt$inlineFun$1", "<init>", "(LA;)V", false);
+      mv.visitTypeInsn(CHECKCAST, "kotlin/jvm/functions/Function0");
+      mv.visitVarInsn(ASTORE, 1);
+      mv.visitLabel(label0);
+      mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      mv.visitVarInsn(ALOAD, 1);
+      mv.visitVarInsn(ASTORE, 4);
+      Label label1 = new Label();
+      // The delayed introduction of the local variable causes a write after the phi.
+      // That write should not be replaced as it invalidates the assumption of local info
+      // associated with phis.
+      mv.visitLabel(label1);
+      mv.visitInsn(ICONST_0);
+      mv.visitVarInsn(ISTORE, 5);
+      Label label2 = new Label();
+      mv.visitLabel(label2);
+      mv.visitLineNumber(13, label2);
+      mv.visitVarInsn(ALOAD, 4);
+      mv.visitMethodInsn(
+          INVOKEINTERFACE,
+          "kotlin/jvm/functions/Function0",
+          "invoke",
+          "()Ljava/lang/Object;",
+          true);
+      mv.visitTypeInsn(CHECKCAST, "A");
+      Label label3 = new Label();
+      mv.visitLabel(label3);
+      mv.visitInsn(ARETURN);
+      mv.visitLocalVariable("$i$f$inlineFun", "I", null, label2, label3, 5);
+      mv.visitLocalVariable(
+          "lambda$iv", "Lkotlin/jvm/functions/Function0;", null, label1, label3, 4);
+      mv.visitMaxs(3, 6);
+      mv.visitEnd();
+
+      cw.visitEnd();
+      return cw.toByteArray();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTest.java b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTest.java
new file mode 100644
index 0000000..7e969c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTest.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2020, 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.debuginfo;
+
+public class DexPcWithDebugInfoForOverloadedMethodsTest {
+
+  private static void inlinee(String message) {
+    if (System.currentTimeMillis() > 0) {
+      throw new RuntimeException(message);
+    }
+  }
+
+  public static void overloaded(int x) {
+    inlinee("overloaded(int)" + x);
+  }
+
+  public static void overloaded(String y) {
+    inlinee("overloaded(String)" + y);
+  }
+
+  public static void main(String[] args) {
+    if (args.length > 0) {
+      overloaded(42);
+    } else {
+      overloaded("42");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
new file mode 100644
index 0000000..b344944
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
@@ -0,0 +1,130 @@
+// Copyright (c) 2020, 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.debuginfo;
+
+import static com.android.tools.r8.Collectors.toSingle;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineFrame;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineStack;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isTopOfStackTrace;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertNull;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.RetraceMethodResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.Matchers.InlinePosition;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 DexPcWithDebugInfoForOverloadedMethodsTestRunner extends TestBase {
+
+  private static final Class<?> MAIN = DexPcWithDebugInfoForOverloadedMethodsTest.class;
+  private static final int MINIFIED_LINE_POSITION = 6;
+  private static final String EXPECTED = "java.lang.RuntimeException: overloaded(String)42";
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimesStartingFromIncluding(Version.V8_1_0)
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.O)
+        .build();
+  }
+
+  public DexPcWithDebugInfoForOverloadedMethodsTestRunner(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testEmittedDebugInfoForOverloads()
+      throws ExecutionException, CompilationFailedException, IOException, NoSuchMethodException {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(MAIN)
+        .addKeepMainRule(MAIN)
+        .addKeepMethodRules(MAIN, "void overloaded(...)")
+        .addKeepAttributeLineNumberTable()
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(
+            internalOptions -> {
+              // TODO(b/37830524): Remove when activated.
+              internalOptions.enablePcDebugInfoOutput = true;
+            })
+        .run(parameters.getRuntime(), MAIN)
+        .assertFailureWithErrorThatMatches(containsString(EXPECTED))
+        .inspectOriginalStackTrace(
+            (stackTrace, inspector) -> {
+              assertEquals(MAIN.getTypeName(), stackTrace.get(0).className);
+              assertEquals("overloaded", stackTrace.get(0).methodName);
+              assertThat(stackTrace.get(0).fileName, not("Unknown Source"));
+              inspect(inspector);
+            })
+        .inspectStackTrace(
+            (stackTrace, codeInspector) -> {
+              MethodSubject throwingSubject =
+                  codeInspector.clazz(MAIN).method("void", "overloaded", "java.lang.String");
+              assertThat(throwingSubject, isPresent());
+              InlinePosition inlineStack =
+                  InlinePosition.stack(
+                      InlinePosition.create(
+                          Reference.methodFromMethod(
+                              MAIN.getDeclaredMethod("inlinee", String.class)),
+                          MINIFIED_LINE_POSITION,
+                          11),
+                      InlinePosition.create(
+                          Reference.methodFromMethod(
+                              MAIN.getDeclaredMethod("overloaded", String.class)),
+                          MINIFIED_LINE_POSITION,
+                          20));
+              RetraceMethodResult retraceResult =
+                  throwingSubject
+                      .streamInstructions()
+                      .filter(InstructionSubject::isThrow)
+                      .collect(toSingle())
+                      .retraceLinePosition(codeInspector.retrace());
+              assertThat(retraceResult, isInlineFrame());
+              assertThat(retraceResult, isInlineStack(inlineStack));
+              assertThat(
+                  retraceResult,
+                  isTopOfStackTrace(
+                      stackTrace,
+                      ImmutableList.of(MINIFIED_LINE_POSITION, MINIFIED_LINE_POSITION)));
+            });
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(MAIN);
+    assertThat(clazz, isPresent());
+    assertEquals(3, clazz.allMethods().size());
+    for (FoundMethodSubject method : clazz.allMethods()) {
+      if (method.getOriginalName().equals("main")) {
+        assertNull(method.getMethod().getCode().asDexCode().getDebugInfo());
+      } else {
+        assertEquals("overloaded", method.getOriginalName());
+        assertNotNull(method.getMethod().getCode().asDexCode().getDebugInfo());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTest.java b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTest.java
new file mode 100644
index 0000000..bca9629
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTest.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2020, 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.debuginfo;
+
+public class EnsureNoDebugInfoEmittedForPcOnlyTest {
+
+  public static void a() {
+    if (System.currentTimeMillis() > 0) {
+      throw new RuntimeException("Hello World!");
+    }
+    System.out.println("Foo");
+    System.out.println("Bar");
+  }
+
+  public static void b() {
+    a();
+  }
+
+  public static void main(String[] args) {
+    if (args.length == 0) {
+      b();
+    } else {
+      other();
+    }
+  }
+
+  public static void other() {
+    System.out.println("FOO");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
new file mode 100644
index 0000000..c47f16f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
@@ -0,0 +1,114 @@
+// Copyright (c) 2020, 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.debuginfo;
+
+import static com.android.tools.r8.Collectors.toSingle;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineFrame;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineStack;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isTopOfStackTrace;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNull;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.RetraceMethodResult;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.Matchers.InlinePosition;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 EnsureNoDebugInfoEmittedForPcOnlyTestRunner extends TestBase {
+
+  private static final Class<?> MAIN = EnsureNoDebugInfoEmittedForPcOnlyTest.class;
+  private static final int INLINED_DEX_PC = 32;
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimesStartingFromIncluding(Version.V8_1_0)
+        .withAllApiLevels()
+        .build();
+  }
+
+  public EnsureNoDebugInfoEmittedForPcOnlyTestRunner(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testNoEmittedDebugInfo()
+      throws ExecutionException, CompilationFailedException, IOException, NoSuchMethodException {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(MAIN)
+        .addKeepMainRule(MAIN)
+        .addKeepAttributeLineNumberTable()
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(
+            internalOptions -> {
+              // TODO(b/37830524): Remove when activated.
+              internalOptions.enablePcDebugInfoOutput = true;
+            })
+        .run(parameters.getRuntime(), MAIN)
+        .inspectOriginalStackTrace(
+            (stackTrace, inspector) -> {
+              assertEquals(MAIN.getTypeName(), stackTrace.get(0).className);
+              assertEquals("main", stackTrace.get(0).methodName);
+              inspect(inspector);
+            })
+        .inspectStackTrace(
+            (stackTrace, codeInspector) -> {
+              MethodSubject mainSubject = codeInspector.clazz(MAIN).uniqueMethodWithName("main");
+              InlinePosition inlineStack =
+                  InlinePosition.stack(
+                      InlinePosition.create(
+                          Reference.methodFromMethod(MAIN.getDeclaredMethod("a")),
+                          INLINED_DEX_PC,
+                          11),
+                      InlinePosition.create(
+                          Reference.methodFromMethod(MAIN.getDeclaredMethod("b")),
+                          INLINED_DEX_PC,
+                          18),
+                      InlinePosition.create(
+                          mainSubject.asFoundMethodSubject().asMethodReference(),
+                          INLINED_DEX_PC,
+                          23));
+              RetraceMethodResult retraceResult =
+                  mainSubject
+                      .streamInstructions()
+                      .filter(InstructionSubject::isThrow)
+                      .collect(toSingle())
+                      .retracePcPosition(codeInspector.retrace(), mainSubject);
+              assertThat(retraceResult, isInlineFrame());
+              assertThat(retraceResult, isInlineStack(inlineStack));
+              assertThat(
+                  retraceResult,
+                  isTopOfStackTrace(
+                      stackTrace,
+                      ImmutableList.of(INLINED_DEX_PC, INLINED_DEX_PC, INLINED_DEX_PC)));
+            });
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(MAIN);
+    assertEquals(1, clazz.allMethods().size());
+    MethodSubject main = clazz.uniqueMethodWithName("main");
+    assertNull(main.getMethod().getCode().asDexCode().getDebugInfo());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/CharSequenceBackportJava11Test.java b/src/test/java/com/android/tools/r8/desugar/backports/CharSequenceBackportJava11Test.java
new file mode 100644
index 0000000..65f2f55
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/CharSequenceBackportJava11Test.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.backports;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+
+@RunWith(Parameterized.class)
+public final class CharSequenceBackportJava11Test extends AbstractBackportTest {
+  @Parameters(name = "{0}")
+  public static Iterable<?> data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withAllApiLevels()
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK11)
+        .build();
+  }
+
+  private static final Path TEST_JAR =
+      Paths.get(ToolHelper.EXAMPLES_JAVA11_JAR_DIR).resolve("backport" + JAR_EXTENSION);
+
+  public CharSequenceBackportJava11Test(TestParameters parameters) {
+    super(parameters, Character.class, TEST_JAR, "backport.CharSequenceBackportJava11Main");
+    // Note: None of the methods in this test exist in the latest android.jar. If/when they ship in
+    // an actual API level, migrate these tests to CharacterBackportTest.
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
index 2c7cbe5..83682b2 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
@@ -13,6 +13,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -34,8 +36,11 @@
       // Check that the backported methods for each API level are are not present in the
       // android.jar for that level.
       CodeInspector inspector = new CodeInspector(ToolHelper.getAndroidJar(apiLevel));
+      InternalOptions options = new InternalOptions();
+      options.minApiLevel = apiLevel.getLevel();
       List<DexMethod> backportedMethods =
-          BackportedMethodRewriter.generateListOfBackportedMethods(apiLevel);
+          BackportedMethodRewriter.generateListOfBackportedMethods(
+              null, options, ThreadUtils.getExecutorService(options));
       for (DexMethod method : backportedMethods) {
         // Two different DexItemFactories are in play, but as toSourceString is used for lookup
         // that is not an issue.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
index eb22ea9..0097b3f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.desugar.desugaredlibrary;
 
 import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.util.ArrayList;
@@ -52,6 +53,26 @@
     assertLines2By2Correct(d8TestRunResult.getStdOut());
   }
 
+  @Test
+  public void testCustomCollectionSuperCallsR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    R8TestRunResult r8TestRunResult =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(CustomCollectionSuperCallsTest.class)
+            .addKeepMainRule(Executor.class)
+            .setMinApi(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .compile()
+            .addDesugaredCoreLibraryRunClassPath(
+                this::buildDesugaredLibrary,
+                parameters.getApiLevel(),
+                keepRuleConsumer.get(),
+                shrinkDesugaredLibrary)
+            .run(parameters.getRuntime(), Executor.class)
+            .assertSuccess();
+    assertLines2By2Correct(r8TestRunResult.getStdOut());
+  }
+
   static class Executor {
 
     // ArrayList spliterator is Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
index 38e8bac..04630af 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
@@ -7,96 +7,137 @@
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertTrue;
 
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import java.nio.file.Path;
 import java.util.List;
 import java.util.function.Consumer;
+import org.junit.BeforeClass;
 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 CallBackConversionTest extends DesugaredLibraryTestBase {
 
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+  private static final String EXPECTED_RESULT = StringUtils.lines("0", "1", "0", "1");
+  private static Path CUSTOM_LIB;
+
+  @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+  }
+
+  public CallBackConversionTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @BeforeClass
+  public static void compileCustomLib() throws Exception {
+    CUSTOM_LIB =
+        testForD8(getStaticTemp())
+            .setMinApi(MIN_SUPPORTED)
+            .addProgramClasses(CustomLibClass.class)
+            .compile()
+            .writeToZip();
+  }
+
+  private static void assertDuplicatedAPI(CodeInspector i) {
+    List<FoundMethodSubject> virtualMethods = i.clazz(Impl.class).virtualMethods();
+    assertEquals(2, virtualMethods.size());
+    assertTrue(
+        virtualMethods.stream()
+            .anyMatch(
+                m ->
+                    m.getMethod()
+                        .method
+                        .proto
+                        .parameters
+                        .values[0]
+                        .toString()
+                        .equals("j$.util.function.Consumer")));
+    assertTrue(
+        virtualMethods.stream()
+            .anyMatch(
+                m ->
+                    m.getMethod()
+                        .method
+                        .proto
+                        .parameters
+                        .values[0]
+                        .toString()
+                        .equals("java.util.function.Consumer")));
+  }
+
   @Test
   public void testCallBack() throws Exception {
-    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
-        .setMinApi(AndroidApiLevel.B)
+        .setMinApi(parameters.getApiLevel())
         .addProgramClasses(Impl.class)
         .addLibraryClasses(CustomLibClass.class)
-        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
-        .inspect(
-            i -> {
-              // foo(j$) and foo(java)
-              List<FoundMethodSubject> virtualMethods = i.clazz(Impl.class).virtualMethods();
-              assertEquals(2, virtualMethods.size());
-              assertTrue(
-                  virtualMethods.stream()
-                      .anyMatch(
-                          m ->
-                              m.getMethod()
-                                  .method
-                                  .proto
-                                  .parameters
-                                  .values[0]
-                                  .toString()
-                                  .equals("j$.util.function.Consumer")));
-              assertTrue(
-                  virtualMethods.stream()
-                      .anyMatch(
-                          m ->
-                              m.getMethod()
-                                  .method
-                                  .proto
-                                  .parameters
-                                  .values[0]
-                                  .toString()
-                                  .equals("java.util.function.Consumer")));
-            })
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
-        .addRunClasspathFiles(customLib)
-        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Impl.class)
-        .assertSuccessWithOutput(StringUtils.lines("0", "1", "0", "1"));
+        .inspect(CallBackConversionTest::assertDuplicatedAPI)
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Impl.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   @Test
   public void testCallBackR8() throws Exception {
-    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(Backend.DEX)
         .addKeepMainRule(Impl.class)
         .noMinification()
-        .setMinApi(AndroidApiLevel.B)
+        .setMinApi(parameters.getApiLevel())
         .addProgramClasses(Impl.class)
         .addLibraryClasses(CustomLibClass.class)
-        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
         .inspect(this::assertLibraryOverridesThere)
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
-        .addRunClasspathFiles(customLib)
-        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Impl.class)
-        .assertSuccessWithOutput(StringUtils.lines("0", "1", "0", "1"));
+        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Impl.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   @Test
   public void testCallBackR8Minifying() throws Exception {
-    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(Backend.DEX)
         .addKeepMainRule(Impl.class)
-        .setMinApi(AndroidApiLevel.B)
+        .setMinApi(parameters.getApiLevel())
         .addProgramClasses(Impl.class)
         .addLibraryClasses(CustomLibClass.class)
-        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
         .inspect(this::assertLibraryOverridesThere)
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
-        .addRunClasspathFiles(customLib)
-        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Impl.class)
-        .assertSuccessWithOutput(StringUtils.lines("0", "1", "0", "1"));
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Impl.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   private void assertLibraryOverridesThere(CodeInspector i) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java
index d4c7f53..4581211 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java
@@ -4,30 +4,87 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
 
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
 import java.time.Clock;
+import java.util.List;
+import org.junit.BeforeClass;
 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 ClockAPIConversionTest extends DesugaredLibraryTestBase {
 
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+  private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.O;
+  private static final String EXPECTED_RESULT = StringUtils.lines("Z", "Z", "true");
+  private static Path CUSTOM_LIB;
+
+  @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+  }
+
+  public ClockAPIConversionTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @BeforeClass
+  public static void compileCustomLib() throws Exception {
+    CUSTOM_LIB =
+        testForD8(getStaticTemp())
+            .addProgramClasses(CustomLibClass.class)
+            .setMinApi(MIN_SUPPORTED)
+            .compile()
+            .writeToZip();
+  }
+
   @Test
-  public void testClock() throws Exception {
-    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+  public void testClockD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
-        .setMinApi(AndroidApiLevel.B)
+        .setMinApi(parameters.getApiLevel())
         .addProgramClasses(Executor.class)
         .addLibraryClasses(CustomLibClass.class)
-        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
-        .addRunClasspathFiles(customLib)
-        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
-        .assertSuccessWithOutput(StringUtils.lines("Z", "Z", "true"));
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testClockR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .addKeepMainRule(Executor.class)
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(Executor.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   static class Executor {
@@ -47,6 +104,7 @@
   // This class is convenient for easy testing. Each method plays the role of methods in the
   // platform APIs for which argument/return values need conversion.
   static class CustomLibClass {
+
     @SuppressWarnings("all")
     public static Clock getClock() {
       return Clock.systemUTC();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
index b13ade6..835d1cb 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
@@ -4,42 +4,145 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
 
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import static com.android.tools.r8.Collectors.toSingle;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.Iterator;
+import java.util.List;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 import org.jetbrains.annotations.NotNull;
+import org.junit.BeforeClass;
 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 ConversionIntroduceInterfaceMethodTest extends DesugaredLibraryTestBase {
 
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "action called from j$ consumer",
+          "forEach called",
+          "action called from java consumer",
+          "forEach called");
+  private static Path CUSTOM_LIB;
+
+  @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+  }
+
+  public ConversionIntroduceInterfaceMethodTest(
+      TestParameters parameters, boolean shrinkDesugaredLibrary) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @BeforeClass
+  public static void compileCustomLib() throws Exception {
+    CUSTOM_LIB =
+        testForD8(getStaticTemp())
+            .setMinApi(MIN_SUPPORTED)
+            .addProgramClasses(CustomLibClass.class)
+            .compile()
+            .writeToZip();
+  }
+
   @Test
-  public void testNoInterfaceMethods() throws Exception {
-    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+  public void testNoInterfaceMethodsD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
-        .setMinApi(AndroidApiLevel.B)
-        .addProgramClasses(
-            MyCollectionInterface.class,
-            MyCollectionInterfaceAbstract.class,
-            MyCollection.class,
-            Executor.class)
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(MyCollectionInterface.class, MyCollection.class, Executor.class)
         .addLibraryClasses(CustomLibClass.class)
-        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .addOptionsModification(opt -> opt.testing.trackDesugaredAPIConversions = true)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
-        .addRunClasspathFiles(customLib)
-        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
-        .assertSuccessWithOutput(
-            StringUtils.lines(
-                "action called from j$ consumer",
-                "forEach called",
-                "action called from java consumer",
-                "forEach called"));
+        .inspect(this::assertDoubleForEach)
+        .inspect(this::assertWrapperMethodsPresent)
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  private void assertDoubleForEach(CodeInspector inspector) {
+    System.out.println(inspector.allClasses().size());
+    FoundClassSubject myCollection =
+        inspector.allClasses().stream()
+            .filter(
+                c ->
+                    c.getOriginalName()
+                            .startsWith(
+                                "com.android.tools.r8.desugar.desugaredlibrary.conversiontests")
+                        && !c.getOriginalName().contains("Executor")
+                        && !c.getOriginalName().contains("$-CC")
+                        && !c.getDexClass().isInterface())
+            .collect(toSingle());
+    assertEquals(
+        "Missing duplicated forEach",
+        2,
+        myCollection.getDexClass().virtualMethods().stream()
+            .filter(m -> m.method.name.toString().equals("forEach"))
+            .count());
+  }
+
+  private void assertWrapperMethodsPresent(CodeInspector inspector) {
+    List<FoundClassSubject> wrappers =
+        inspector.allClasses().stream()
+            .filter(
+                c ->
+                    !c.getFinalName()
+                        .startsWith(
+                            "com.android.tools.r8.desugar.desugaredlibrary.conversiontests"))
+            .collect(Collectors.toList());
+    for (FoundClassSubject wrapper : wrappers) {
+      assertTrue(wrapper.virtualMethods().size() > 0);
+    }
+  }
+
+  @Test
+  public void testNoInterfaceMethodsR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(MyCollectionInterface.class, MyCollection.class, Executor.class)
+        .addKeepMainRule(Executor.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .inspect(this::assertDoubleForEach)
+        .inspect(this::assertWrapperMethodsPresent)
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   static class CustomLibClass {
@@ -91,7 +194,6 @@
       return false;
     }
 
-    @NotNull
     @Override
     public Iterator<E> iterator() {
       return null;
@@ -142,12 +244,4 @@
     @Override
     public void clear() {}
   }
-
-  interface MyCollectionInterfaceAbstract<E> extends Collection<E> {
-
-    // The following method override a method from Iterable and use a desugared type.
-    // API conversion is required.
-    @Override
-    void forEach(Consumer<? super E> action);
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java
index 2699bd3..b62045b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java
@@ -6,33 +6,94 @@
 
 import static junit.framework.TestCase.assertEquals;
 
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.conversiontests.MoreFunctionConversionTest.CustomLibClass;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.nio.file.Path;
 import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.function.BiConsumer;
+import org.junit.BeforeClass;
 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 DuplicateAPIProgramTest extends DesugaredLibraryTestBase {
 
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+  private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+  private static Path CUSTOM_LIB;
+
+  @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+  }
+
+  public DuplicateAPIProgramTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @BeforeClass
+  public static void compileCustomLib() throws Exception {
+    CUSTOM_LIB =
+        testForD8(getStaticTemp())
+            .addProgramClasses(CustomLibClass.class)
+            .setMinApi(MIN_SUPPORTED)
+            .compile()
+            .writeToZip();
+  }
+
   @Test
-  public void testMap() throws Exception {
-    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+  public void testMapD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     String stdOut =
         testForD8()
-            .setMinApi(AndroidApiLevel.B)
+            .setMinApi(parameters.getApiLevel())
             .addProgramClasses(Executor.class, MyMap.class)
             .addLibraryClasses(CustomLibClass.class)
-            .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile()
             .inspect(this::assertDupMethod)
-            .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
-            .addRunClasspathFiles(customLib)
-            .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
+            .addDesugaredCoreLibraryRunClassPath(
+                this::buildDesugaredLibrary,
+                parameters.getApiLevel(),
+                keepRuleConsumer.get(),
+                shrinkDesugaredLibrary)
+            .addRunClasspathFiles(CUSTOM_LIB)
+            .run(parameters.getRuntime(), Executor.class)
+            .assertSuccess()
+            .getStdOut();
+    assertLines2By2Correct(stdOut);
+  }
+
+  @Test
+  public void testMapR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    String stdOut =
+        testForR8(parameters.getBackend())
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(Executor.class)
+            .addProgramClasses(Executor.class, MyMap.class)
+            .addLibraryClasses(CustomLibClass.class)
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .compile()
+            .inspect(this::assertDupMethod)
+            .addDesugaredCoreLibraryRunClassPath(
+                this::buildDesugaredLibrary,
+                parameters.getApiLevel(),
+                keepRuleConsumer.get(),
+                shrinkDesugaredLibrary)
+            .addRunClasspathFiles(CUSTOM_LIB)
+            .run(parameters.getRuntime(), Executor.class)
             .assertSuccess()
             .getStdOut();
     assertLines2By2Correct(stdOut);
@@ -72,6 +133,7 @@
   // This class is convenient for easy testing. Each method plays the role of methods in the
   // platform APIs for which argument/return values need conversion.
   static class CustomLibClass {
+
     @SuppressWarnings("WeakerAccess")
     public static <K, V> void javaForEach(Map<K, V> map, BiConsumer<K, V> consumer) {
       map.forEach(consumer);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
index 1dfbc73..66cdcbc 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
@@ -6,11 +6,12 @@
 
 import static junit.framework.TestCase.assertEquals;
 
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.conversiontests.MoreFunctionConversionTest.CustomLibClass;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
@@ -27,26 +28,84 @@
 import java.util.function.LongSupplier;
 import java.util.stream.Collectors;
 import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.BeforeClass;
 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 FunctionConversionTest extends DesugaredLibraryTestBase {
 
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+  private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(" true true true", "2", "false", "3", "true", "5", "42.0");
+  private static Path CUSTOM_LIB;
+
+  @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+  }
+
+  public FunctionConversionTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @BeforeClass
+  public static void compileCustomLib() throws Exception {
+    CUSTOM_LIB =
+        testForD8(getStaticTemp())
+            .addProgramClasses(CustomLibClass.class)
+            .setMinApi(MIN_SUPPORTED)
+            .compile()
+            .writeToZip();
+  }
+
   @Test
-  public void testFunctionComposition() throws Exception {
-    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+  public void testFunctionCompositionD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
-        .setMinApi(AndroidApiLevel.B)
+        .setMinApi(parameters.getApiLevel())
         .addProgramClasses(
             Executor.class, Executor.Object1.class, Executor.Object2.class, Executor.Object3.class)
         .addLibraryClasses(CustomLibClass.class)
-        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
         .inspect(this::assertSingleWrappers)
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
-        .addRunClasspathFiles(customLib)
-        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
-        .assertSuccessWithOutput(
-            StringUtils.lines("Object1 Object2 Object3", "2", "false", "3", "true", "5", "42.0"));
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testFunctionCompositionR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Executor.class)
+        .addProgramClasses(
+            Executor.class, Executor.Object1.class, Executor.Object2.class, Executor.Object3.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   private void assertSingleWrappers(CodeInspector i) {
@@ -71,12 +130,14 @@
 
   @Test
   public void testWrapperWithChecksum() throws Exception {
+    Assume.assumeTrue(
+        shrinkDesugaredLibrary && parameters.getApiLevel().getLevel() <= MIN_SUPPORTED.getLevel());
     testForD8()
         .addProgramClasses(
             Executor.class, Executor.Object1.class, Executor.Object2.class, Executor.Object3.class)
         .addLibraryClasses(CustomLibClass.class)
-        .setMinApi(AndroidApiLevel.B)
-        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel())
         .setIncludeClassesChecksum(true) // Compilation fails if some classes are missing checksum.
         .compile()
         .inspect(
@@ -145,11 +206,12 @@
 
       @Override
       public String toString() {
-        return field.field.getClass().getSimpleName()
+        return " "
+            + (field.field.getClass() == Object1.class)
             + " "
-            + field.getClass().getSimpleName()
+            + (field.getClass() == Object2.class)
             + " "
-            + getClass().getSimpleName();
+            + (getClass() == Object3.class);
       }
     }
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/MoreFunctionConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/MoreFunctionConversionTest.java
index eea96ae..10823c3 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/MoreFunctionConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/MoreFunctionConversionTest.java
@@ -7,38 +7,95 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.D8TestCompileResult;
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.google.common.collect.Sets;
 import java.nio.file.Path;
+import java.util.List;
 import java.util.Set;
 import java.util.function.Function;
+import org.junit.BeforeClass;
 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 MoreFunctionConversionTest extends DesugaredLibraryTestBase {
 
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+  private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+  private static final String EXPECTED_RESULT = StringUtils.lines("6", "6", "6", "6", "6");
+  private static Path CUSTOM_LIB;
+
+  @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+  }
+
+  public MoreFunctionConversionTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @BeforeClass
+  public static void compileCustomLib() throws Exception {
+    CUSTOM_LIB =
+        testForD8(getStaticTemp())
+            .addProgramClasses(CustomLibClass.class)
+            .setMinApi(MIN_SUPPORTED)
+            .compile()
+            .writeToZip();
+  }
+
   @Test
-  public void testFunction() throws Exception {
-    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+  public void testFunctionD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     D8TestCompileResult compileResult =
         testForD8()
-            .setMinApi(AndroidApiLevel.B)
+            .setMinApi(parameters.getApiLevel())
             .addProgramClasses(Executor.class)
             .addLibraryClasses(CustomLibClass.class)
-            .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile();
     Path program = compileResult.writeToZip();
-    assertNoDuplicateLambdas(program, customLib);
+    assertNoDuplicateLambdas(program, CUSTOM_LIB);
     compileResult
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
-        .addRunClasspathFiles(customLib)
-        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
-        .assertSuccessWithOutput(StringUtils.lines("6", "6", "6", "6", "6"));
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testFunctionR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(Executor.class)
+        .addKeepMainRule(Executor.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   // If we have the exact same lambda in both, but one implements j$..Function and the other
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
index bebefd1..af4088e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
@@ -5,15 +5,18 @@
 package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
 
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.conversiontests.MoreFunctionConversionTest.CustomLibClass;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.DoubleSummaryStatistics;
 import java.util.IntSummaryStatistics;
+import java.util.List;
 import java.util.LongSummaryStatistics;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -23,40 +26,72 @@
 public class SummaryStatisticsConversionTest extends DesugaredLibraryTestBase {
 
   private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+  private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "2", "1", "42", "42", "42", "1", "42", "42", "42", "1", "42.0", "42.0", "42.0");
+  private static Path CUSTOM_LIB;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    // Below 7 XXXSummaryStatistics are not present and conversions are pointless.
-    return getTestParameters()
-        .withDexRuntimesStartingFromIncluding(Version.V7_0_0)
-        .withAllApiLevels()
-        .build();
+  @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
   }
 
-  public SummaryStatisticsConversionTest(TestParameters parameters) {
+  public SummaryStatisticsConversionTest(
+      TestParameters parameters, boolean shrinkDesugaredLibrary) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
     this.parameters = parameters;
   }
 
-  @Test
-  public void testStats() throws Exception {
-    Path customLib =
-        testForD8()
-            .setMinApi(parameters.getApiLevel())
+  @BeforeClass
+  public static void compileCustomLib() throws Exception {
+    CUSTOM_LIB =
+        testForD8(getStaticTemp())
             .addProgramClasses(CustomLibClass.class)
+            .setMinApi(MIN_SUPPORTED)
             .compile()
             .writeToZip();
+  }
+
+  @Test
+  public void testStatsD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .setMinApi(parameters.getApiLevel())
         .addProgramClasses(Executor.class)
         .addLibraryClasses(CustomLibClass.class)
-        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
-        .addRunClasspathFiles(customLib)
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
         .run(parameters.getRuntime(), Executor.class)
-        .assertSuccessWithOutput(
-            StringUtils.lines(
-                "2", "1", "42", "42", "42", "1", "42", "42", "42", "1", "42.0", "42.0", "42.0"));
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testStatsR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(Executor.class)
+        .addKeepMainRule(Executor.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   static class Executor {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/TryCatchTimeConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/TryCatchTimeConversionTest.java
index 3589bb3..4dbcccb 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/TryCatchTimeConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/TryCatchTimeConversionTest.java
@@ -4,46 +4,130 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
 
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.conversiontests.SummaryStatisticsConversionTest.CustomLibClass;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
 import java.time.ZoneId;
+import java.util.List;
+import org.junit.BeforeClass;
 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 TryCatchTimeConversionTest extends DesugaredLibraryTestBase {
 
-  @Test
-  public void testBaseline() throws Exception {
-    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
-    testForD8()
-        .setMinApi(AndroidApiLevel.B)
-        .addProgramClasses(BaselineExecutor.class)
-        .addLibraryClasses(CustomLibClass.class)
-        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
-        .compile()
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
-        .addRunClasspathFiles(customLib)
-        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), BaselineExecutor.class)
-        .assertSuccessWithOutput(StringUtils.lines("GMT", "GMT", "GMT", "GMT", "GMT"));
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+  private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.O;
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines("GMT", "GMT", "GMT", "GMT", "GMT");
+  private static final String EXPECTED_RESULT_EXCEPTION =
+      StringUtils.lines("GMT", "GMT", "GMT", "GMT", "GMT", "Exception caught");
+  private static Path CUSTOM_LIB;
+
+  @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+  }
+
+  public TryCatchTimeConversionTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @BeforeClass
+  public static void compileCustomLib() throws Exception {
+    CUSTOM_LIB =
+        testForD8(getStaticTemp())
+            .addProgramClasses(CustomLibClass.class)
+            .setMinApi(MIN_SUPPORTED)
+            .compile()
+            .writeToZip();
   }
 
   @Test
-  public void testTryCatch() throws Exception {
-    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+  public void testBaselineD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
-        .setMinApi(AndroidApiLevel.B)
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(BaselineExecutor.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), BaselineExecutor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testBaselineR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(BaselineExecutor.class)
+        .addKeepMainRule(BaselineExecutor.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), BaselineExecutor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testTryCatchD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .setMinApi(parameters.getApiLevel())
         .addProgramClasses(TryCatchExecutor.class)
         .addLibraryClasses(CustomLibClass.class)
-        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
-        .addRunClasspathFiles(customLib)
-        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), TryCatchExecutor.class)
-        .assertSuccessWithOutput(
-            StringUtils.lines("GMT", "GMT", "GMT", "GMT", "GMT", "Exception caught"));
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), TryCatchExecutor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT_EXCEPTION);
+  }
+
+  @Test
+  public void testTryCatchR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(TryCatchExecutor.class)
+        .addKeepMainRule(TryCatchExecutor.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), TryCatchExecutor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT_EXCEPTION);
   }
 
   @SuppressWarnings("WeakerAccess")
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/UnwrapConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/UnwrapConversionTest.java
index 646d20f..2f10f3a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/UnwrapConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/UnwrapConversionTest.java
@@ -4,31 +4,89 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
 
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.conversiontests.SummaryStatisticsConversionTest.CustomLibClass;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
+import java.util.List;
 import java.util.function.DoubleConsumer;
 import java.util.function.IntConsumer;
+import org.junit.BeforeClass;
 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 UnwrapConversionTest extends DesugaredLibraryTestBase {
 
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+  private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+  private static final String EXPECTED_RESULT = StringUtils.lines("true", "true");
+  private static Path CUSTOM_LIB;
+
+  @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+  }
+
+  public UnwrapConversionTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @BeforeClass
+  public static void compileCustomLib() throws Exception {
+    CUSTOM_LIB =
+        testForD8(getStaticTemp())
+            .addProgramClasses(CustomLibClass.class)
+            .setMinApi(MIN_SUPPORTED)
+            .compile()
+            .writeToZip();
+  }
+
   @Test
-  public void testUnwrap() throws Exception {
-    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+  public void testUnwrapD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
-        .setMinApi(AndroidApiLevel.B)
+        .setMinApi(parameters.getApiLevel())
         .addProgramClasses(Executor.class)
         .addLibraryClasses(CustomLibClass.class)
-        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
-        .addRunClasspathFiles(customLib)
-        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
-        .assertSuccessWithOutput(StringUtils.lines("true", "true"));
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testUnwrapR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(Executor.class)
+        .addKeepMainRule(Executor.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   static class Executor {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java
new file mode 100644
index 0000000..ddcb725
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingAnalysisTest.java
@@ -0,0 +1,110 @@
+// Copyright (c) 2020, 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.enumunboxing;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 ComparisonEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+  private final Class<?>[] INPUTS = new Class<?>[] {NullCheck.class, EnumComparison.class};
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public ComparisonEnumUnboxingAnalysisTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    R8TestCompileResult compile =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(ComparisonEnumUnboxingAnalysisTest.class)
+            .addKeepMainRules(INPUTS)
+            .addKeepRules(KEEP_ENUM)
+            .enableInliningAnnotations()
+            .addOptionsModification(this::enableEnumOptions)
+            .allowDiagnosticInfoMessages()
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspect(
+                inspector -> {
+                  assertThat(
+                      inspector.clazz(NullCheck.class).uniqueMethodWithName("nullCheck"),
+                      isPresent());
+                  assertThat(
+                      inspector.clazz(EnumComparison.class).uniqueMethodWithName("check"),
+                      isPresent());
+                });
+    for (Class<?> input : INPUTS) {
+      R8TestRunResult run =
+          compile
+              .inspectDiagnosticMessages(
+                  m -> assertEnumIsUnboxed(input.getDeclaredClasses()[0], input.getSimpleName(), m))
+              .run(parameters.getRuntime(), input)
+              .assertSuccess();
+      assertLines2By2Correct(run.getStdOut());
+    }
+  }
+
+  @SuppressWarnings("ConstantConditions")
+  static class NullCheck {
+
+    enum MyEnum {
+      A,
+      B
+    }
+
+    public static void main(String[] args) {
+      System.out.println(nullCheck(MyEnum.A));
+      System.out.println(false);
+      System.out.println(nullCheck(MyEnum.B));
+      System.out.println(false);
+      System.out.println(nullCheck(null));
+      System.out.println(true);
+    }
+
+    // Do not resolve the == with constants after inlining.
+    @NeverInline
+    static boolean nullCheck(MyEnum e) {
+      return e == null;
+    }
+  }
+
+  static class EnumComparison {
+
+    enum MyEnum {
+      A,
+      B
+    }
+
+    public static void main(String[] args) {
+      System.out.println(check(MyEnum.A));
+      System.out.println(false);
+      System.out.println(check(MyEnum.B));
+      System.out.println(true);
+    }
+
+    // Do not resolve the == with constants after inlining.
+    @NeverInline
+    static boolean check(MyEnum e) {
+      return e == MyEnum.B;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
index e4f7bd1..c7ad117 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
@@ -23,8 +23,6 @@
 public class FailingMethodEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
 
   private static final Class<?>[] FAILURES = {
-    NullCheck.class,
-    Check.class,
     InstanceFieldPutObject.class,
     StaticFieldPutObject.class,
     ToString.class,
@@ -74,8 +72,6 @@
 
   private void assertEnumsAsExpected(CodeInspector inspector) {
     // Check all as expected (else we test nothing)
-    assertTrue(inspector.clazz(NullCheck.class).uniqueMethodWithName("nullCheck").isPresent());
-    assertTrue(inspector.clazz(Check.class).uniqueMethodWithName("check").isPresent());
 
     assertEquals(
         1, inspector.clazz(InstanceFieldPutObject.class).getDexClass().instanceFields().size());
@@ -85,50 +81,6 @@
     assertTrue(inspector.clazz(FailingPhi.class).uniqueMethodWithName("switchOn").isPresent());
   }
 
-  @SuppressWarnings("ConstantConditions")
-  static class NullCheck {
-
-    enum MyEnum {
-      A,
-      B,
-      C
-    }
-
-    public static void main(String[] args) {
-      System.out.println(nullCheck(MyEnum.A));
-      System.out.println(false);
-      System.out.println(nullCheck(null));
-      System.out.println(true);
-    }
-
-    // Do not resolve the == with constants after inlining.
-    @NeverInline
-    static boolean nullCheck(MyEnum e) {
-      return e == null;
-    }
-  }
-
-  static class Check {
-
-    enum MyEnum {
-      A,
-      B,
-      C
-    }
-
-    public static void main(String[] args) {
-      MyEnum e1 = MyEnum.A;
-      System.out.println(check(e1));
-      System.out.println(false);
-    }
-
-    // Do not resolve the == with constants after inlining.
-    @NeverInline
-    static boolean check(MyEnum e) {
-      return e == MyEnum.B;
-    }
-  }
-
   static class InstanceFieldPutObject {
 
     enum MyEnum {
diff --git a/src/test/java/com/android/tools/r8/graph/DexTypeTest.java b/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
index 90218e0..1b5ffa9 100644
--- a/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
+++ b/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
@@ -24,7 +24,7 @@
   @BeforeClass
   public static void makeAppInfo() throws Exception {
     InternalOptions options = new InternalOptions();
-    DexApplication application =
+    DirectMappedDexApplication application =
         new ApplicationReader(
                 AndroidApp.builder()
                     .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialTest.java
index 44b676a..642c263 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialTest.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptor;
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.AsmTestBase;
 import com.android.tools.r8.ByteDataView;
@@ -63,6 +64,7 @@
 
   @Test
   public void testDXBehavior() throws Exception {
+    assumeTrue(ToolHelper.artSupported());
     testForDX()
         .addProgramFiles(inputJar)
         .run(Main.class)
diff --git a/src/test/java/com/android/tools/r8/internal/Iosched2019Test.java b/src/test/java/com/android/tools/r8/internal/Iosched2019Test.java
new file mode 100644
index 0000000..34d0e01
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/Iosched2019Test.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.internal;
+
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class Iosched2019Test extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public Iosched2019Test(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    assumeTrue(ToolHelper.isLocalDevelopment());
+
+    List<Path> programFiles =
+        Files.list(Paths.get(ToolHelper.THIRD_PARTY_DIR, "iosched_2019"))
+            .filter(path -> path.toString().endsWith(".jar"))
+            .collect(Collectors.toList());
+    assertEquals(155, programFiles.size());
+
+    testForR8(parameters.getBackend())
+        .addProgramFiles(programFiles)
+        .addKeepRuleFiles(
+            Paths.get(ToolHelper.THIRD_PARTY_DIR, "iosched_2019", "proguard-rules.pro"))
+        .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+        .allowDiagnosticMessages()
+        .allowUnusedProguardConfigurationRules()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .assertAllInfoMessagesMatch(
+            anyOf(
+                containsString("Ignoring option: "),
+                containsString("Proguard configuration rule does not match anything: ")))
+        .assertAllWarningMessagesMatch(
+            anyOf(
+                containsString("Missing class: "),
+                containsString("required for default or static interface methods desugaring"),
+                equalTo("Resource 'META-INF/MANIFEST.MF' already exists.")))
+        .assertNoErrorMessages();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
index 78585b8..0924546 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -60,13 +60,15 @@
 
     // Check lookup targets with include method.
     Set<DexEncodedMethod> targets =
-        appInfo.resolveMethodOnClass(clazz, method.method).lookupVirtualTargets(appInfo);
+        appInfo.resolveMethodOnClass(clazz, method.method).lookupVirtualDispatchTargets(appInfo);
     assertTrue(targets.contains(method));
   }
 
   private void testInterfaceLookup(DexProgramClass clazz, DexEncodedMethod method) {
     Set<DexEncodedMethod> targets =
-        appInfo.resolveMethodOnInterface(clazz, method.method).lookupInterfaceTargets(appInfo);
+        appInfo
+            .resolveMethodOnInterface(clazz, method.method)
+            .lookupVirtualDispatchTargets(appInfo);
     if (appInfo.subtypes(method.method.holder).stream()
         .allMatch(t -> appInfo.definitionFor(t).isInterface())) {
       assertEquals(
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index 34d7c61..a2b9dce 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -48,7 +48,7 @@
       List<IRCode> additionalCode)
       throws ExecutionException {
     AppView<AppInfoWithSubtyping> appView =
-        AppView.createForR8(new AppInfoWithSubtyping(application), options);
+        AppView.createForR8(new AppInfoWithSubtyping(application.asDirect()), options);
     appView.setAppServices(AppServices.builder(appView).build());
     ExecutorService executorService = ThreadUtils.getExecutorService(options);
     appView.setRootSet(
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java b/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java
index 7321e79..ebfb64f 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.utils.AndroidApp;
@@ -64,7 +64,7 @@
 
   @Before
   public void setup() throws Exception {
-    DexApplication application =
+    DirectMappedDexApplication application =
         new ApplicationReader(app, options, Timing.empty()).read().toDirect();
     appView = AppView.createForR8(new AppInfoWithSubtyping(application), options);
   }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index 979de40..6a4bd17 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -18,11 +18,11 @@
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
@@ -122,7 +122,7 @@
             ? ClassFileConsumer.emptyConsumer()
             : DexIndexedConsumer.emptyConsumer();
     Timing timing = Timing.empty();
-    DexApplication application =
+    DirectMappedDexApplication application =
         new ApplicationReader(
                 AndroidApp.builder()
                     .addClassProgramData(
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 e0465b3..cac6ef5 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
@@ -17,9 +17,9 @@
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -50,7 +50,7 @@
     D8Command.Builder d8CommandBuilder = D8Command.builder();
     d8CommandBuilder.addProgramFiles(testClassPaths);
     AndroidApp testClassApp = ToolHelper.runD8(d8CommandBuilder);
-    DexApplication application =
+    DirectMappedDexApplication application =
         new ApplicationReader(
                 AndroidApp.builder(testClassApp)
                     .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/CycleEliminationTest.java b/src/test/java/com/android/tools/r8/ir/conversion/CycleEliminationTest.java
index f38629f..c9ae9fd 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/CycleEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/CycleEliminationTest.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
 import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator;
-import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import java.util.ArrayList;
@@ -55,15 +54,15 @@
       method.addCallerConcurrently(forceInlinedMethod);
 
       // Check that the cycle eliminator finds the cycle.
-      CycleEliminator cycleEliminator = new CycleEliminator(nodes, new InternalOptions());
-      assertEquals(1, cycleEliminator.breakCycles().numberOfRemovedEdges());
+      CycleEliminator cycleEliminator = new CycleEliminator();
+      assertEquals(1, cycleEliminator.breakCycles(nodes).numberOfRemovedCallEdges());
 
       // The edge from method to forceInlinedMethod should be removed to ensure that force inlining
       // will work.
       assertTrue(forceInlinedMethod.isLeaf());
 
       // Check that the cycle eliminator agrees that there are no more cycles left.
-      assertEquals(0, cycleEliminator.breakCycles().numberOfRemovedEdges());
+      assertEquals(0, cycleEliminator.breakCycles(nodes).numberOfRemovedCallEdges());
     }
   }
 
@@ -76,11 +75,10 @@
     forceInlinedMethod.addCallerConcurrently(method);
     method.addCallerConcurrently(forceInlinedMethod);
 
-    CycleEliminator cycleEliminator =
-        new CycleEliminator(ImmutableList.of(method, forceInlinedMethod), new InternalOptions());
+    CycleEliminator cycleEliminator = new CycleEliminator();
 
     try {
-      cycleEliminator.breakCycles();
+      cycleEliminator.breakCycles(ImmutableList.of(method, forceInlinedMethod));
       fail("Force inlining should fail");
     } catch (CompilationError e) {
       assertThat(e.toString(), containsString(CycleEliminator.CYCLIC_FORCE_INLINING_MESSAGE));
@@ -170,9 +168,9 @@
       }
 
       // Check that the cycle eliminator finds the cycles.
-      CycleEliminator cycleEliminator =
-          new CycleEliminator(configuration.nodes, new InternalOptions());
-      int numberOfCycles = cycleEliminator.breakCycles().numberOfRemovedEdges();
+      CycleEliminator cycleEliminator = new CycleEliminator();
+      int numberOfCycles =
+          cycleEliminator.breakCycles(configuration.nodes).numberOfRemovedCallEdges();
       if (numberOfCycles == 1) {
         // If only one cycle was removed, then it must be the edge from n1 -> n2 that was removed.
         assertTrue(n1.isLeaf());
@@ -182,7 +180,7 @@
       }
 
       // Check that the cycle eliminator agrees that there are no more cycles left.
-      assertEquals(0, cycleEliminator.breakCycles().numberOfRemovedEdges());
+      assertEquals(0, cycleEliminator.breakCycles(configuration.nodes).numberOfRemovedCallEdges());
 
       // Check that force inlining is guaranteed to succeed.
       if (configuration.test != null) {
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java b/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
index 2d1f0e5..4088693 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
@@ -11,16 +11,12 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
 import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator;
-import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.Sets;
 import java.util.Set;
 import java.util.TreeSet;
 import org.junit.Test;
 
 public class NodeExtractionTest extends CallGraphTestBase {
 
-  private InternalOptions options = new InternalOptions();
-
   // Note that building a test graph is intentionally repeated to avoid race conditions and/or
   // non-deterministic test results due to cycle elimination.
 
@@ -50,24 +46,21 @@
     nodes.add(n5);
     nodes.add(n6);
 
-    Set<Node> wave = Sets.newIdentityHashSet();
-
-    PrimaryMethodProcessor.extractLeaves(nodes, wave::add);
+    CallGraph cg = new CallGraph(nodes);
+    Set<DexEncodedMethod> wave = cg.extractLeaves();
     assertEquals(3, wave.size());
-    assertThat(wave, hasItem(n3));
-    assertThat(wave, hasItem(n4));
-    assertThat(wave, hasItem(n6));
-    wave.clear();
+    assertThat(wave, hasItem(n3.method));
+    assertThat(wave, hasItem(n4.method));
+    assertThat(wave, hasItem(n6.method));
 
-    PrimaryMethodProcessor.extractLeaves(nodes, wave::add);
+    wave = cg.extractLeaves();
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(n2));
-    assertThat(wave, hasItem(n5));
-    wave.clear();
+    assertThat(wave, hasItem(n2.method));
+    assertThat(wave, hasItem(n5.method));
 
-    PrimaryMethodProcessor.extractLeaves(nodes, wave::add);
+    wave = cg.extractLeaves();
     assertEquals(1, wave.size());
-    assertThat(wave, hasItem(n1));
+    assertThat(wave, hasItem(n1.method));
     assertTrue(nodes.isEmpty());
   }
 
@@ -99,27 +92,26 @@
 
     n1.addCallerConcurrently(n3);
     n3.method.getMutableOptimizationInfo().markForceInline();
-    CycleEliminator cycleEliminator = new CycleEliminator(nodes, options);
-    assertEquals(1, cycleEliminator.breakCycles().numberOfRemovedEdges());
+    CycleEliminator cycleEliminator = new CycleEliminator();
+    assertEquals(1, cycleEliminator.breakCycles(nodes).numberOfRemovedCallEdges());
 
-    Set<Node> wave = Sets.newIdentityHashSet();
-
-    PrimaryMethodProcessor.extractLeaves(nodes, wave::add);
+    CallGraph cg = new CallGraph(nodes);
+    Set<DexEncodedMethod> wave = cg.extractLeaves();
     assertEquals(3, wave.size());
-    assertThat(wave, hasItem(n3));
-    assertThat(wave, hasItem(n4));
-    assertThat(wave, hasItem(n6));
+    assertThat(wave, hasItem(n3.method));
+    assertThat(wave, hasItem(n4.method));
+    assertThat(wave, hasItem(n6.method));
     wave.clear();
 
-    PrimaryMethodProcessor.extractLeaves(nodes, wave::add);
+    wave = cg.extractLeaves();
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(n2));
-    assertThat(wave, hasItem(n5));
+    assertThat(wave, hasItem(n2.method));
+    assertThat(wave, hasItem(n5.method));
     wave.clear();
 
-    PrimaryMethodProcessor.extractLeaves(nodes, wave::add);
+    wave = cg.extractLeaves();
     assertEquals(1, wave.size());
-    assertThat(wave, hasItem(n1));
+    assertThat(wave, hasItem(n1.method));
     assertTrue(nodes.isEmpty());
   }
 
@@ -150,21 +142,17 @@
     nodes.add(n6);
 
     CallGraph callGraph = new CallGraph(nodes, null);
-    Set<DexEncodedMethod> wave = Sets.newIdentityHashSet();
-
-    wave.addAll(callGraph.extractRoots());
+    Set<DexEncodedMethod> wave = callGraph.extractRoots();
     assertEquals(2, wave.size());
     assertThat(wave, hasItem(n1.method));
     assertThat(wave, hasItem(n5.method));
-    wave.clear();
 
-    wave.addAll(callGraph.extractRoots());
+    wave = callGraph.extractRoots();
     assertEquals(2, wave.size());
     assertThat(wave, hasItem(n2.method));
     assertThat(wave, hasItem(n6.method));
-    wave.clear();
 
-    wave.addAll(callGraph.extractRoots());
+    wave = callGraph.extractRoots();
     assertEquals(2, wave.size());
     assertThat(wave, hasItem(n3.method));
     assertThat(wave, hasItem(n4.method));
@@ -199,25 +187,21 @@
 
     n1.addCallerConcurrently(n3);
     n3.method.getMutableOptimizationInfo().markForceInline();
-    CycleEliminator cycleEliminator = new CycleEliminator(nodes, options);
-    assertEquals(1, cycleEliminator.breakCycles().numberOfRemovedEdges());
+    CycleEliminator cycleEliminator = new CycleEliminator();
+    assertEquals(1, cycleEliminator.breakCycles(nodes).numberOfRemovedCallEdges());
 
     CallGraph callGraph = new CallGraph(nodes, null);
-    Set<DexEncodedMethod> wave = Sets.newIdentityHashSet();
-
-    wave.addAll(callGraph.extractRoots());
+    Set<DexEncodedMethod> wave = callGraph.extractRoots();
     assertEquals(2, wave.size());
     assertThat(wave, hasItem(n1.method));
     assertThat(wave, hasItem(n5.method));
-    wave.clear();
 
-    wave.addAll(callGraph.extractRoots());
+    wave = callGraph.extractRoots();
     assertEquals(2, wave.size());
     assertThat(wave, hasItem(n2.method));
     assertThat(wave, hasItem(n6.method));
-    wave.clear();
 
-    wave.addAll(callGraph.extractRoots());
+    wave = callGraph.extractRoots();
     assertEquals(2, wave.size());
     assertThat(wave, hasItem(n3.method));
     assertThat(wave, hasItem(n4.method));
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
index 6b18b6b..1955c47 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
@@ -14,9 +14,9 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.Enqueuer;
@@ -42,7 +42,8 @@
   public PartialCallGraphTest() throws Exception {
     Timing timing = Timing.empty();
     AndroidApp app = testForD8().addProgramClasses(TestClass.class).compile().app;
-    DexApplication application = new ApplicationReader(app, options, timing).read().toDirect();
+    DirectMappedDexApplication application =
+        new ApplicationReader(app, options, timing).read().toDirect();
     AppView<AppInfoWithSubtyping> appView =
         AppView.createForR8(new AppInfoWithSubtyping(application), options);
     appView.setAppServices(AppServices.builder(appView).build());
@@ -80,24 +81,20 @@
     assertNotNull(m5);
     assertNotNull(m6);
 
-    Set<Node> wave = Sets.newIdentityHashSet();
-
-    PrimaryMethodProcessor.extractLeaves(cg.nodes, wave::add);
+    Set<DexEncodedMethod> wave = cg.extractLeaves();
     assertEquals(4, wave.size()); // including <init>
-    assertThat(wave, hasItem(m3));
-    assertThat(wave, hasItem(m4));
-    assertThat(wave, hasItem(m6));
-    wave.clear();
+    assertThat(wave, hasItem(m3.method));
+    assertThat(wave, hasItem(m4.method));
+    assertThat(wave, hasItem(m6.method));
 
-    PrimaryMethodProcessor.extractLeaves(cg.nodes, wave::add);
+    wave = cg.extractLeaves();
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(m2));
-    assertThat(wave, hasItem(m5));
-    wave.clear();
+    assertThat(wave, hasItem(m2.method));
+    assertThat(wave, hasItem(m5.method));
 
-    PrimaryMethodProcessor.extractLeaves(cg.nodes, wave::add);
+    wave = cg.extractLeaves();
     assertEquals(1, wave.size());
-    assertThat(wave, hasItem(m1));
+    assertThat(wave, hasItem(m1.method));
     assertTrue(cg.nodes.isEmpty());
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/CharSequenceMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/CharSequenceMethods.java
new file mode 100644
index 0000000..5030c6d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/CharSequenceMethods.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2020, 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.desugar.backports;
+
+public final class CharSequenceMethods {
+
+  public static int compare(CharSequence a, CharSequence b) {
+    int aLen = a.length(); // implicit null check
+    int bLen = b.length(); // implicit null check
+    if (a == b) {
+      return 0;
+    }
+    for (int i = 0, stop = Math.min(aLen, bLen); i < stop; i++) {
+      char aChar = a.charAt(i);
+      char bChar = b.charAt(i);
+      if (aChar != bChar) {
+        return (int) aChar - (int) bChar; // See CharacterMethods.compare
+      }
+    }
+    // Prefixes match so we need to return a value based on which is longer.
+    return aLen - bLen;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index e5c7948..306c93a 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -71,6 +71,7 @@
       ImmutableList.of(
           BooleanMethods.class,
           ByteMethods.class,
+          CharSequenceMethods.class,
           CharacterMethods.class,
           CloseResourceMethod.class,
           CollectionMethods.class,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/B148366506.java b/src/test/java/com/android/tools/r8/ir/optimize/B148366506.java
new file mode 100644
index 0000000..e89e6cd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/B148366506.java
@@ -0,0 +1,388 @@
+// Copyright (c) 2020, 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.CompilationMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class B148366506 extends TestBase implements Opcodes {
+
+  private final TestParameters parameters;
+  private final CompilationMode compilationMode;
+
+  @Parameterized.Parameters(name = "{0}, {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(), CompilationMode.values());
+  }
+
+  public B148366506(TestParameters parameters, CompilationMode compilationMode) {
+    this.parameters = parameters;
+    this.compilationMode = compilationMode;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForD8()
+        .addProgramClassFileData(dump())
+        .setMinApi(parameters.getApiLevel())
+        .setMode(compilationMode)
+        .compile();
+  }
+
+  // Code from b/148366506.
+  public static byte[] dump() {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(
+        V1_7,
+        ACC_PUBLIC | ACC_SUPER,
+        "d/b/c/e/e/a/b/a",
+        null,
+        "java/lang/Object",
+        new String[] {"com/microsoft/identity/common/adal/internal/cache/IStorageHelper"});
+
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_SYNCHRONIZED, "e", "()Ljavax/crypto/SecretKey;", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      Label label1 = new Label();
+      Label label2 = new Label();
+      methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/Exception");
+      Label label3 = new Label();
+      Label label4 = new Label();
+      Label label5 = new Label();
+      methodVisitor.visitTryCatchBlock(label3, label4, label5, null);
+      Label label6 = new Label();
+      methodVisitor.visitTryCatchBlock(label6, label5, label2, "java/lang/Exception");
+      Label label7 = new Label();
+      Label label8 = new Label();
+      Label label9 = new Label();
+      methodVisitor.visitTryCatchBlock(label7, label8, label9, null);
+      Label label10 = new Label();
+      methodVisitor.visitJumpInsn(GOTO, label10);
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitFrame(
+          Opcodes.F_FULL, 0, new Object[] {}, 1, new Object[] {"java/lang/Exception"});
+      methodVisitor.visitInsn(ATHROW);
+      Label label11 = new Label();
+      methodVisitor.visitLabel(label11);
+      methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"d/b/c/e/e/a/b/a"});
+      methodVisitor.visitInsn(ICONST_0);
+      Label label12 = new Label();
+      methodVisitor.visitJumpInsn(GOTO, label12);
+      Label label13 = new Label();
+      methodVisitor.visitLabel(label13);
+      methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[] {"d/b/c/e/e/a/b/a"}, 0, null);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitFieldInsn(
+          GETFIELD, "d/b/c/e/e/a/b/a", "mContext", "Landroid/content/Context;");
+      methodVisitor.visitLabel(label1);
+      Label label14 = new Label();
+      methodVisitor.visitJumpInsn(GOTO, label14);
+      Label label15 = new Label();
+      methodVisitor.visitLabel(label15);
+      methodVisitor.visitFrame(
+          Opcodes.F_SAME1, 0, null, 1, new Object[] {"android/content/Context"});
+      methodVisitor.visitFieldInsn(GETSTATIC, "d/b/c/e/e/a/b/a", "\u0131", "I");
+      methodVisitor.visitIntInsn(BIPUSH, 111);
+      methodVisitor.visitInsn(IADD);
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitIntInsn(SIPUSH, 128);
+      methodVisitor.visitInsn(IREM);
+      methodVisitor.visitFieldInsn(PUTSTATIC, "d/b/c/e/e/a/b/a", "\u03b9", "I");
+      methodVisitor.visitInsn(ICONST_2);
+      methodVisitor.visitInsn(IREM);
+      Label label16 = new Label();
+      methodVisitor.visitJumpInsn(IFNE, label16);
+      Label label17 = new Label();
+      methodVisitor.visitJumpInsn(GOTO, label17);
+      methodVisitor.visitLabel(label16);
+      methodVisitor.visitFrame(
+          Opcodes.F_SAME1, 0, null, 1, new Object[] {"android/content/Context"});
+      methodVisitor.visitJumpInsn(GOTO, label3);
+      Label label18 = new Label();
+      methodVisitor.visitLabel(label18);
+      methodVisitor.visitFrame(
+          Opcodes.F_FULL,
+          0,
+          new Object[] {},
+          2,
+          new Object[] {"d/b/c/e/e/a/b/a", "java/lang/String"});
+      methodVisitor.visitInsn(ICONST_2);
+      methodVisitor.visitInsn(ICONST_2);
+      methodVisitor.visitInsn(IREM);
+      methodVisitor.visitInsn(POP);
+      methodVisitor.visitJumpInsn(GOTO, label6);
+      methodVisitor.visitLabel(label3);
+      methodVisitor.visitFrame(
+          Opcodes.F_FULL,
+          1,
+          new Object[] {"d/b/c/e/e/a/b/a"},
+          1,
+          new Object[] {"android/content/Context"});
+      methodVisitor.visitLdcInsn("android.content.Context");
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "java/lang/Class",
+          "forName",
+          "(Ljava/lang/String;)Ljava/lang/Class;",
+          false);
+      methodVisitor.visitLdcInsn("getPackageName");
+      methodVisitor.visitInsn(ACONST_NULL);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/lang/Class",
+          "getMethod",
+          "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;",
+          false);
+      methodVisitor.visitInsn(SWAP);
+      methodVisitor.visitInsn(ACONST_NULL);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/lang/reflect/Method",
+          "invoke",
+          "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;",
+          false);
+      methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/String");
+      methodVisitor.visitLabel(label4);
+      methodVisitor.visitVarInsn(ASTORE, 1);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitMethodInsn(
+          INVOKESPECIAL, "d/b/c/e/e/a/b/a", "getSecretKeyData", "(Ljava/lang/String;)[B", false);
+      methodVisitor.visitVarInsn(ASTORE, 2);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitVarInsn(ALOAD, 2);
+      Label label19 = new Label();
+      methodVisitor.visitJumpInsn(IFNONNULL, label19);
+      Label label20 = new Label();
+      methodVisitor.visitJumpInsn(GOTO, label20);
+      methodVisitor.visitLabel(label19);
+      methodVisitor.visitFrame(
+          Opcodes.F_FULL, 0, new Object[] {}, 1, new Object[] {"d/b/c/e/e/a/b/a"});
+      Label label21 = new Label();
+      methodVisitor.visitJumpInsn(GOTO, label21);
+      Label label22 = new Label();
+      methodVisitor.visitLabel(label22);
+      methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"d/b/c/e/e/a/b/a"});
+      methodVisitor.visitLdcInsn("A001");
+      methodVisitor.visitJumpInsn(GOTO, label6);
+      methodVisitor.visitLabel(label12);
+      methodVisitor.visitFrame(
+          Opcodes.F_FULL, 0, new Object[] {}, 2, new Object[] {"d/b/c/e/e/a/b/a", Opcodes.INTEGER});
+      Label label23 = new Label();
+      methodVisitor.visitTableSwitchInsn(0, 1, label22, new Label[] {label22, label23});
+      Label label24 = new Label();
+      methodVisitor.visitLabel(label24);
+      methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"d/b/c/e/e/a/b/a"});
+      methodVisitor.visitInsn(ICONST_1);
+      methodVisitor.visitJumpInsn(GOTO, label12);
+      methodVisitor.visitLabel(label9);
+      methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"});
+      methodVisitor.visitInsn(ATHROW);
+      methodVisitor.visitLabel(label6);
+      methodVisitor.visitFrame(
+          Opcodes.F_FULL,
+          0,
+          new Object[] {},
+          2,
+          new Object[] {"d/b/c/e/e/a/b/a", "java/lang/String"});
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "d/b/c/e/e/a/b/a",
+          "loadSecretKeyForEncryption",
+          "(Ljava/lang/String;)Ljavax/crypto/SecretKey;",
+          false);
+      methodVisitor.visitInsn(ARETURN);
+      methodVisitor.visitLabel(label5);
+      methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"});
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Throwable", "getCause", "()Ljava/lang/Throwable;", false);
+      methodVisitor.visitInsn(DUP);
+      Label label25 = new Label();
+      methodVisitor.visitJumpInsn(IFNULL, label25);
+      Label label26 = new Label();
+      methodVisitor.visitJumpInsn(GOTO, label26);
+      methodVisitor.visitLabel(label25);
+      methodVisitor.visitFrame(
+          Opcodes.F_FULL,
+          0,
+          new Object[] {},
+          2,
+          new Object[] {"java/lang/Throwable", "java/lang/Throwable"});
+      Label label27 = new Label();
+      methodVisitor.visitJumpInsn(GOTO, label27);
+      Label label28 = new Label();
+      methodVisitor.visitLabel(label28);
+      methodVisitor.visitFrame(
+          Opcodes.F_FULL,
+          1,
+          new Object[] {"d/b/c/e/e/a/b/a"},
+          1,
+          new Object[] {"android/content/Context"});
+      methodVisitor.visitInsn(ICONST_2);
+      methodVisitor.visitInsn(ICONST_2);
+      methodVisitor.visitInsn(IREM);
+      methodVisitor.visitInsn(POP);
+      methodVisitor.visitJumpInsn(GOTO, label15);
+      Label label29 = new Label();
+      methodVisitor.visitLabel(label29);
+      methodVisitor.visitFrame(
+          Opcodes.F_FULL, 0, new Object[] {}, 1, new Object[] {"d/b/c/e/e/a/b/a"});
+      methodVisitor.visitLdcInsn("U001");
+      methodVisitor.visitJumpInsn(GOTO, label18);
+      methodVisitor.visitLabel(label10);
+      methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[] {"d/b/c/e/e/a/b/a"}, 0, null);
+      methodVisitor.visitInsn(ICONST_2);
+      methodVisitor.visitInsn(ICONST_2);
+      methodVisitor.visitInsn(IREM);
+      methodVisitor.visitInsn(POP);
+      Label label30 = new Label();
+      methodVisitor.visitJumpInsn(GOTO, label30);
+      methodVisitor.visitLabel(label20);
+      methodVisitor.visitFrame(
+          Opcodes.F_FULL, 0, new Object[] {}, 1, new Object[] {"d/b/c/e/e/a/b/a"});
+      methodVisitor.visitInsn(ICONST_1);
+      Label label31 = new Label();
+      methodVisitor.visitJumpInsn(GOTO, label31);
+      Label label32 = new Label();
+      methodVisitor.visitLabel(label32);
+      methodVisitor.visitFrame(
+          Opcodes.F_FULL,
+          1,
+          new Object[] {"d/b/c/e/e/a/b/a"},
+          1,
+          new Object[] {"android/content/Context"});
+      methodVisitor.visitJumpInsn(GOTO, label28);
+      methodVisitor.visitLabel(label31);
+      methodVisitor.visitFrame(
+          Opcodes.F_FULL, 0, new Object[] {}, 2, new Object[] {"d/b/c/e/e/a/b/a", Opcodes.INTEGER});
+      Label label33 = new Label();
+      methodVisitor.visitTableSwitchInsn(0, 1, label29, new Label[] {label29, label33});
+      methodVisitor.visitLabel(label26);
+      methodVisitor.visitFrame(
+          Opcodes.F_FULL,
+          0,
+          new Object[] {},
+          2,
+          new Object[] {"java/lang/Throwable", "java/lang/Throwable"});
+      methodVisitor.visitInsn(SWAP);
+      methodVisitor.visitInsn(POP);
+      methodVisitor.visitInsn(ATHROW);
+      methodVisitor.visitLabel(label21);
+      methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"d/b/c/e/e/a/b/a"});
+      methodVisitor.visitInsn(ICONST_0);
+      methodVisitor.visitJumpInsn(GOTO, label31);
+      methodVisitor.visitLabel(label17);
+      methodVisitor.visitFrame(
+          Opcodes.F_FULL,
+          1,
+          new Object[] {"d/b/c/e/e/a/b/a"},
+          1,
+          new Object[] {"android/content/Context"});
+      methodVisitor.visitJumpInsn(GOTO, label3);
+      methodVisitor.visitLabel(label30);
+      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      methodVisitor.visitFieldInsn(GETSTATIC, "d/b/c/e/e/a/b/a", "\u0131", "I");
+      methodVisitor.visitIntInsn(BIPUSH, 47);
+      methodVisitor.visitInsn(IADD);
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitIntInsn(SIPUSH, 128);
+      methodVisitor.visitInsn(IREM);
+      methodVisitor.visitFieldInsn(PUTSTATIC, "d/b/c/e/e/a/b/a", "\u03b9", "I");
+      methodVisitor.visitInsn(ICONST_2);
+      methodVisitor.visitInsn(IREM);
+      Label label34 = new Label();
+      methodVisitor.visitJumpInsn(IFNE, label34);
+      methodVisitor.visitJumpInsn(GOTO, label8);
+      methodVisitor.visitLabel(label34);
+      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      methodVisitor.visitJumpInsn(GOTO, label13);
+      methodVisitor.visitLabel(label33);
+      methodVisitor.visitFrame(
+          Opcodes.F_FULL, 0, new Object[] {}, 1, new Object[] {"d/b/c/e/e/a/b/a"});
+      methodVisitor.visitFieldInsn(GETSTATIC, "d/b/c/e/e/a/b/a", "\u03b9", "I");
+      methodVisitor.visitIntInsn(BIPUSH, 35);
+      methodVisitor.visitInsn(IADD);
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitIntInsn(SIPUSH, 128);
+      methodVisitor.visitInsn(IREM);
+      methodVisitor.visitFieldInsn(PUTSTATIC, "d/b/c/e/e/a/b/a", "\u0131", "I");
+      methodVisitor.visitInsn(ICONST_2);
+      methodVisitor.visitInsn(IREM);
+      Label label35 = new Label();
+      methodVisitor.visitJumpInsn(IFEQ, label35);
+      methodVisitor.visitJumpInsn(GOTO, label24);
+      methodVisitor.visitLabel(label35);
+      methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"d/b/c/e/e/a/b/a"});
+      methodVisitor.visitJumpInsn(GOTO, label11);
+      methodVisitor.visitLabel(label14);
+      methodVisitor.visitFrame(
+          Opcodes.F_FULL,
+          1,
+          new Object[] {"d/b/c/e/e/a/b/a"},
+          1,
+          new Object[] {"android/content/Context"});
+      methodVisitor.visitFieldInsn(GETSTATIC, "d/b/c/e/e/a/b/a", "\u03b9", "I");
+      methodVisitor.visitIntInsn(BIPUSH, 45);
+      methodVisitor.visitInsn(IADD);
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitIntInsn(SIPUSH, 128);
+      methodVisitor.visitInsn(IREM);
+      methodVisitor.visitFieldInsn(PUTSTATIC, "d/b/c/e/e/a/b/a", "\u0131", "I");
+      methodVisitor.visitInsn(ICONST_2);
+      methodVisitor.visitInsn(IREM);
+      Label label36 = new Label();
+      methodVisitor.visitJumpInsn(IFEQ, label36);
+      methodVisitor.visitJumpInsn(GOTO, label32);
+      methodVisitor.visitLabel(label36);
+      methodVisitor.visitFrame(
+          Opcodes.F_SAME1, 0, null, 1, new Object[] {"android/content/Context"});
+      methodVisitor.visitJumpInsn(GOTO, label28);
+      methodVisitor.visitLabel(label27);
+      methodVisitor.visitFrame(
+          Opcodes.F_FULL,
+          0,
+          new Object[] {},
+          2,
+          new Object[] {"java/lang/Throwable", "java/lang/Throwable"});
+      methodVisitor.visitInsn(POP);
+      methodVisitor.visitInsn(ATHROW);
+      methodVisitor.visitLabel(label23);
+      methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"d/b/c/e/e/a/b/a"});
+      methodVisitor.visitLdcInsn("A001");
+      methodVisitor.visitLabel(label7);
+      methodVisitor.visitInsn(ACONST_NULL);
+      methodVisitor.visitInsn(ARRAYLENGTH);
+      methodVisitor.visitInsn(POP);
+      methodVisitor.visitJumpInsn(GOTO, label6);
+      methodVisitor.visitLabel(label8);
+      methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[] {"d/b/c/e/e/a/b/a"}, 0, null);
+      methodVisitor.visitJumpInsn(GOTO, label13);
+      methodVisitor.visitMaxs(4, 3);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java
index a24de2f..41eba43 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java
@@ -9,9 +9,9 @@
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.utils.AndroidApp;
@@ -27,7 +27,7 @@
   @BeforeClass
   public static void makeAppInfo() throws Exception {
     InternalOptions options = new InternalOptions();
-    DexApplication application =
+    DirectMappedDexApplication application =
         new ApplicationReader(
                 AndroidApp.builder().addLibraryFiles(ToolHelper.getDefaultAndroidJar()).build(),
                 options,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
index 838a681..15456be 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
@@ -20,7 +20,8 @@
     Timing timing = Timing.empty();
     AndroidApp app = buildAndroidApp(ToolHelper.getClassAsBytes(mainClass));
     InternalOptions options = new InternalOptions();
-    DexApplication dexApplication = new ApplicationReader(app, options, timing).read().toDirect();
+    DirectMappedDexApplication dexApplication =
+        new ApplicationReader(app, options, timing).read().toDirect();
     AppView<?> appView = AppView.createForD8(new AppInfoWithSubtyping(dexApplication), options);
     appView.setAppServices(AppServices.builder(appView).build());
     return appView;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
index 7db31b4..a963edf 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -46,6 +47,7 @@
         .withCfRuntimes()
         // Objects#requireNonNull will be desugared VMs older than API level K.
         .withDexRuntimesStartingFromExcluding(Version.V4_4_4)
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.K)
         .build();
   }
 
@@ -111,7 +113,7 @@
         testForD8()
             .debug()
             .addProgramClassesAndInnerClasses(MAIN)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
     test(result, 2, 1);
@@ -120,7 +122,7 @@
         testForD8()
             .release()
             .addProgramClassesAndInnerClasses(MAIN)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
     test(result, 0, 1);
@@ -136,7 +138,7 @@
             .enableMemberValuePropagationAnnotations()
             .addKeepMainRule(MAIN)
             .noMinification()
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
     test(result, 0, 0);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineInvokeWithNullableReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineInvokeWithNullableReceiverTest.java
index c00b563..d8b26ba 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineInvokeWithNullableReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineInvokeWithNullableReceiverTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -32,7 +33,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection params() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public InlineInvokeWithNullableReceiverTest(TestParameters parameters) {
@@ -45,7 +46,7 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(InlineInvokeWithNullableReceiverTest.class)
             .addKeepMainRule(TestClass.class)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .compile()
             .inspect(this::verifyMethodHasBeenInlined);
 
@@ -67,7 +68,21 @@
     assertThat(methodSubject, isPresent());
 
     // A `throw` instruction should have been synthesized into main().
-    assertTrue(methodSubject.streamInstructions().anyMatch(InstructionSubject::isThrow));
+    if (parameters.isCfRuntime()
+        || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K)) {
+      assertTrue(methodSubject.streamInstructions().anyMatch(InstructionSubject::isInvokeStatic));
+    } else {
+      assertTrue(
+          methodSubject
+              .streamInstructions()
+              .filter(InstructionSubject::isInvokeVirtual)
+              .anyMatch(
+                  method ->
+                      method
+                          .getMethod()
+                          .toSourceString()
+                          .equals("java.lang.Class java.lang.Object.getClass()")));
+    }
 
     // Class A is still present because the instance flows into a phi that has a null-check.
     ClassSubject otherClassSubject = inspector.clazz(A.class);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/ConstClassMemberValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/ConstClassMemberValuePropagationTest.java
new file mode 100644
index 0000000..af4af19
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/ConstClassMemberValuePropagationTest.java
@@ -0,0 +1,98 @@
+// Copyright (c) 2020, 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.membervaluepropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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 ConstClassMemberValuePropagationTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ConstClassMemberValuePropagationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ConstClassMemberValuePropagationTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+    assertThat(
+        testClassSubject.uniqueMethodWithName("deadDueToFieldValuePropagation"), not(isPresent()));
+    assertThat(
+        testClassSubject.uniqueMethodWithName("deadDueToReturnValuePropagation"), not(isPresent()));
+
+    // Verify that there are no more conditional instructions.
+    MethodSubject mainMethodSubject = testClassSubject.mainMethod();
+    assertThat(mainMethodSubject, isPresent());
+    assertTrue(mainMethodSubject.streamInstructions().noneMatch(InstructionSubject::isIf));
+  }
+
+  static class TestClass {
+
+    static Class<?> INSTANCE = TestClass.class;
+
+    public static void main(String[] args) {
+      if (INSTANCE == TestClass.class) {
+        System.out.print("Hello");
+      } else {
+        deadDueToFieldValuePropagation();
+      }
+      if (get() == TestClass.class) {
+        System.out.println(" world!");
+      } else {
+        deadDueToReturnValuePropagation();
+      }
+    }
+
+    @NeverInline
+    static Class<?> get() {
+      return TestClass.class;
+    }
+
+    @NeverInline
+    static void deadDueToFieldValuePropagation() {
+      throw new RuntimeException();
+    }
+
+    @NeverInline
+    static void deadDueToReturnValuePropagation() {
+      throw new RuntimeException();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java
index 4b8ceff..d230621 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.membervaluepropagation;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 
@@ -61,8 +62,8 @@
                     assertTrue(readFieldsWaveIndex >= 0);
                     int writeFieldsWaveIndex =
                         IterableUtils.firstIndexMatching(waves, wavePredicate.apply("writeFields"));
-                    // TODO(b/144739580): Should be false.
-                    assertTrue(writeFieldsWaveIndex >= readFieldsWaveIndex);
+                    assertTrue(writeFieldsWaveIndex >= 0);
+                    assertTrue(writeFieldsWaveIndex < readFieldsWaveIndex);
                   };
             })
         .enableInliningAnnotations()
@@ -79,8 +80,7 @@
     ClassSubject testClassSubject = inspector.clazz(TestClass.class);
     assertThat(testClassSubject, isPresent());
     assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
-    // TODO(b/144739580): Should be absent.
-    assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+    assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/nonnull/RequireNonNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/RequireNonNullTest.java
new file mode 100644
index 0000000..a6f8740
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/RequireNonNullTest.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2020, 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;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.Objects;
+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 RequireNonNullTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public RequireNonNullTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(RequireNonNullTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Live!", "Live!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
+    assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Object a = System.currentTimeMillis() >= 0 ? new Object() : null;
+      Objects.requireNonNull(a);
+      if (a != null) {
+        live();
+      } else {
+        dead();
+      }
+
+      Object b = System.currentTimeMillis() >= 0 ? new Object() : null;
+      Object c = Objects.requireNonNull(b);
+      if (c != null) {
+        live();
+      } else {
+        dead();
+      }
+    }
+
+    @NeverInline
+    static void live() {
+      System.out.println("Live!");
+    }
+
+    @NeverInline
+    static void dead() {
+      System.out.println("Dead!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
index cf4a324..c2fa7fb 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.optimize.reflection;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
@@ -155,7 +156,7 @@
     assertThat(mainMethod, isPresent());
     int expectedCount = isR8 ? (isRelease ? 0 : 5) : 6;
     assertEquals(expectedCount, countGetClass(mainMethod));
-    expectedCount = isR8 ? (isRelease ? (parameters.isCfRuntime() ? 7 : 5) : 1) : 0;
+    expectedCount = isR8 ? (isRelease ? (parameters.isCfRuntime() ? 8 : 6) : 1) : 0;
     assertEquals(expectedCount, countConstClass(mainMethod));
 
     boolean expectToBeOptimized = isR8 && isRelease;
@@ -167,10 +168,14 @@
     assertEquals(0, countConstClass(getMainClass));
 
     MethodSubject call = mainClass.method("java.lang.Class", "call", ImmutableList.of());
-    assertThat(call, isPresent());
-    // Because of local, only R8 release mode can rewrite getClass() to const-class.
-    assertEquals(expectToBeOptimized ? 0 : 1, countGetClass(call));
-    assertEquals(expectToBeOptimized ? 1 : 0, countConstClass(call));
+    if (isR8 && isRelease) {
+      assertThat(call, not(isPresent()));
+    } else {
+      assertThat(call, isPresent());
+      // Because of local, only R8 release mode can rewrite getClass() to const-class.
+      assertEquals(expectToBeOptimized ? 0 : 1, countGetClass(call));
+      assertEquals(expectToBeOptimized ? 1 : 0, countConstClass(call));
+    }
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java
index c8d3f1c..f19fa6f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java
@@ -10,14 +10,12 @@
 
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.ForceInline;
-import com.android.tools.r8.JvmTestRunResult;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRunResult;
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -49,11 +47,10 @@
   @Test
   public void testJVMOutput() throws Exception {
     assumeTrue("Only run JVM reference on CF runtimes", parameters.isCfRuntime());
-    JvmTestRunResult run = testForJvm().addTestClasspath().run(parameters.getRuntime(), MAIN);
-    // TODO(b/119097175): Fix test
-    if (!ToolHelper.isWindows()) {
-      run.assertSuccessWithOutput(JAVA_OUTPUT);
-    }
+    testForJvm()
+        .addTestClasspath()
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutput(JAVA_OUTPUT);
   }
 
   private long countNonZeroConstNumber(MethodSubject method) {
@@ -102,12 +99,9 @@
             .enableInliningAnnotations()
             .addKeepMainRule(MAIN)
             .setMinApi(parameters.getApiLevel())
-            .run(parameters.getRuntime(), MAIN);
-    // TODO(b/119097175): Fix test
-    if (!ToolHelper.isWindows()) {
-      result.assertSuccessWithOutput(JAVA_OUTPUT);
-      test(result, 0, parameters.isDexRuntime() ? 6 : 7);
-    }
+            .run(parameters.getRuntime(), MAIN)
+            .assertSuccessWithOutput(JAVA_OUTPUT);
+    test(result, 0, parameters.isDexRuntime() ? 6 : 7);
   }
 
   public static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index f88783d..911479e 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -59,17 +59,13 @@
     }
 
     @Override
-    public void replaceCurrentInstructionWithConstInt(
-        AppView<? extends AppInfoWithSubtyping> appView, IRCode code, int value) {
+    public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
       throw new Unimplemented();
     }
 
     @Override
     public void replaceCurrentInstructionWithStaticGet(
-        AppView<? extends AppInfoWithSubtyping> appView,
-        IRCode code,
-        DexField field,
-        Set<Value> affectedValues) {
+        AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues) {
       throw new Unimplemented();
     }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
index 5059a1e..16385b4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmPropertySubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.nio.file.Path;
 import java.util.Collection;
@@ -62,19 +63,15 @@
   }
 
   @Test
-  public void testMetadataInCompanion_renamed() throws Exception {
+  public void testMetadataInCompanion_kept() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
             .addProgramFiles(companionLibJarMap.get(targetVersion))
-            // Keep the B class and its interface (which has the doStuff method).
-            .addKeepRules("-keep class **.B")
-            .addKeepRules("-keep class **.I { <methods>; }")
-            // Keep getters for B$Companion.(singleton|foo) which will be referenced at the app.
-            .addKeepRules("-keepclassmembers class **.B$* { *** get*(...); }")
-            // No rule for Super, but will be kept and renamed.
+            // Keep everything
+            .addKeepRules("-keep class **.companion_lib.** { *; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
             .compile()
-            .inspect(this::inspect)
+            .inspect(codeInspector -> inspect(codeInspector, true))
             .writeToZip();
 
     ProcessResult kotlinTestCompileResult =
@@ -87,17 +84,52 @@
 
     // TODO(b/70169921): should be able to compile!
     assertNotEquals(0, kotlinTestCompileResult.exitCode);
-    assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: singleton"));
+    assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: elt2"));
+  }
+
+  @Test
+  public void testMetadataInCompanion_renamed() throws Exception {
+    Path libJar =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(companionLibJarMap.get(targetVersion))
+            // Keep the B class and its interface (which has the doStuff method).
+            .addKeepRules("-keep class **.B")
+            .addKeepRules("-keep class **.I { <methods>; }")
+            // Keep getters for B$Companion.(eltN|foo) which will be referenced at the app.
+            .addKeepRules("-keepclassmembers class **.B$* { *** get*(...); }")
+            // No rule for Super, but will be kept and renamed.
+            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .compile()
+            .inspect(codeInspector -> inspect(codeInspector, false))
+            .writeToZip();
+
+    ProcessResult kotlinTestCompileResult =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/companion_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            // TODO(b/70169921): update to just .compile() once fixed.
+            .compileRaw();
+
+    // TODO(b/70169921): should be able to compile!
+    assertNotEquals(0, kotlinTestCompileResult.exitCode);
+    assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: elt1"));
+    assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: elt2"));
     assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: foo"));
   }
 
-  private void inspect(CodeInspector inspector) {
+  private void inspect(CodeInspector inspector, boolean keptAll) {
     final String superClassName = PKG + ".companion_lib.Super";
     final String bClassName = PKG + ".companion_lib.B";
     final String companionClassName = PKG + ".companion_lib.B$Companion";
 
     ClassSubject sup = inspector.clazz(superClassName);
-    assertThat(sup, isRenamed());
+    if (keptAll) {
+      assertThat(sup, isPresent());
+      assertThat(sup, not(isRenamed()));
+    } else {
+      assertThat(sup, isRenamed());
+    }
 
     ClassSubject impl = inspector.clazz(bClassName);
     assertThat(impl, isPresent());
@@ -106,26 +138,60 @@
     KmClassSubject kmClass = impl.getKmClass();
     assertThat(kmClass, isPresent());
     List<ClassSubject> superTypes = kmClass.getSuperTypes();
-    assertTrue(superTypes.stream().noneMatch(
-        supertype -> supertype.getFinalDescriptor().contains("Super")));
-    assertTrue(superTypes.stream().anyMatch(
-        supertype -> supertype.getFinalDescriptor().equals(sup.getFinalDescriptor())));
+    if (!keptAll) {
+      assertTrue(superTypes.stream().noneMatch(
+          supertype -> supertype.getFinalDescriptor().contains("Super")));
+      assertTrue(superTypes.stream().anyMatch(
+          supertype -> supertype.getFinalDescriptor().equals(sup.getFinalDescriptor())));
+    }
 
     // Bridge for the property in the companion that needs a backing field.
-    MethodSubject singletonBridge = impl.uniqueMethodWithName("access$getSingleton$cp");
-    assertThat(singletonBridge, isRenamed());
+    MethodSubject elt1Bridge = impl.uniqueMethodWithName("access$getElt1$cp");
+    if (keptAll) {
+      assertThat(elt1Bridge, isPresent());
+      assertThat(elt1Bridge, not(isRenamed()));
+    } else {
+      assertThat(elt1Bridge, isRenamed());
+    }
+    // With @JvmField, no bridge is added.
+    MethodSubject elt2Bridge = impl.uniqueMethodWithName("access$getElt2$cp");
+    assertThat(elt2Bridge, not(isPresent()));
 
-    // For B$Companion.foo, no backing field needed, hence no bridge.
+    // For B$Companion.foo, which is a simple computation, no backing field needed, hence no bridge.
     MethodSubject fooBridge = impl.uniqueMethodWithName("access$getFoo$cp");
     assertThat(fooBridge, not(isPresent()));
 
     ClassSubject companion = inspector.clazz(companionClassName);
-    assertThat(companion, isRenamed());
+    if (keptAll) {
+      assertThat(companion, isPresent());
+      assertThat(companion, not(isRenamed()));
+    } else {
+      assertThat(companion, isRenamed());
+    }
 
-    MethodSubject singletonGetter = companion.uniqueMethodWithName("getSingleton");
-    assertThat(singletonGetter, isPresent());
+    // TODO(b/70169921): Assert impl's KmClass points to the correct companion object and class.
+
+    kmClass = companion.getKmClass();
+    assertThat(kmClass, isPresent());
+
+    KmPropertySubject kmProperty = kmClass.kmPropertyWithUniqueName("elt1");
+    assertThat(kmProperty, isPresent());
+    // TODO(b/70169921): property in companion with @JvmField is missing.
+    kmProperty = kmClass.kmPropertyWithUniqueName("elt2");
+    assertThat(kmProperty, not(isPresent()));
+    kmProperty = kmClass.kmPropertyWithUniqueName("foo");
+    assertThat(kmProperty, isPresent());
+
+    MethodSubject elt1Getter = companion.uniqueMethodWithName("getElt1");
+    assertThat(elt1Getter, isPresent());
+    assertThat(elt1Getter, not(isRenamed()));
+
+    // Note that there is no getter for property with @JvmField.
+    MethodSubject elt2Getter = companion.uniqueMethodWithName("getElt2");
+    assertThat(elt2Getter, not(isPresent()));
 
     MethodSubject fooGetter = companion.uniqueMethodWithName("getFoo");
     assertThat(fooGetter, isPresent());
+    assertThat(fooGetter, not(isRenamed()));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
index 917ede7..1f8fee7 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
@@ -7,8 +7,10 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
@@ -20,6 +22,7 @@
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
 import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeSubject;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.HashMap;
@@ -94,7 +97,6 @@
   private void inspectMerged(CodeInspector inspector) {
     String superClassName = PKG + ".extension_function_lib.Super";
     String bClassName = PKG + ".extension_function_lib.B";
-    String bKtClassName = PKG + ".extension_function_lib.BKt";
 
     assertThat(inspector.clazz(superClassName), not(isPresent()));
 
@@ -108,15 +110,7 @@
     assertTrue(superTypes.stream().noneMatch(
         supertype -> supertype.getFinalDescriptor().contains("Super")));
 
-    ClassSubject bKt = inspector.clazz(bKtClassName);
-    assertThat(bKt, isPresent());
-    assertThat(bKt, not(isRenamed()));
-    // API entry is kept, hence the presence of Metadata.
-    KmPackageSubject kmPackage = bKt.getKmPackage();
-    assertThat(kmPackage, isPresent());
-
-    KmFunctionSubject kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("extension");
-    assertThat(kmFunction, isExtensionFunction());
+    inspectExtensions(inspector);
   }
 
   @Test
@@ -154,7 +148,6 @@
   private void inspectRenamed(CodeInspector inspector) {
     String superClassName = PKG + ".extension_function_lib.Super";
     String bClassName = PKG + ".extension_function_lib.B";
-    String bKtClassName = PKG + ".extension_function_lib.BKt";
 
     ClassSubject sup = inspector.clazz(superClassName);
     assertThat(sup, isPresent());
@@ -172,6 +165,16 @@
     assertTrue(superTypes.stream().anyMatch(
         supertype -> supertype.getFinalDescriptor().equals(sup.getFinalDescriptor())));
 
+    inspectExtensions(inspector);
+  }
+
+  private void inspectExtensions(CodeInspector inspector) {
+    String bClassName = PKG + ".extension_function_lib.B";
+    String bKtClassName = PKG + ".extension_function_lib.BKt";
+
+    ClassSubject impl = inspector.clazz(bClassName);
+    assertThat(impl, isPresent());
+
     ClassSubject bKt = inspector.clazz(bKtClassName);
     assertThat(bKt, isPresent());
     assertThat(bKt, not(isRenamed()));
@@ -181,5 +184,29 @@
 
     KmFunctionSubject kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("extension");
     assertThat(kmFunction, isExtensionFunction());
+    KmTypeSubject kmTypeSubject = kmFunction.receiverParameterType();
+    assertEquals(impl.getFinalDescriptor(), kmTypeSubject.descriptor());
+
+    kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("csHash");
+    assertThat(kmFunction, isExtensionFunction());
+    kmTypeSubject = kmFunction.receiverParameterType();
+    assertEquals("Lkotlin/CharSequence;", kmTypeSubject.descriptor());
+    kmTypeSubject = kmFunction.returnType();
+    assertEquals("Lkotlin/Long;", kmTypeSubject.descriptor());
+
+    kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("longArrayHash");
+    assertThat(kmFunction, isExtensionFunction());
+    kmTypeSubject = kmFunction.receiverParameterType();
+    assertEquals("Lkotlin/LongArray;", kmTypeSubject.descriptor());
+    kmTypeSubject = kmFunction.returnType();
+    assertEquals("Lkotlin/Long;", kmTypeSubject.descriptor());
+
+    kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("myApply");
+    assertThat(kmFunction, isExtensionFunction());
+    kmTypeSubject = kmFunction.receiverParameterType();
+    assertEquals(impl.getFinalDescriptor(), kmTypeSubject.descriptor());
+    // TODO(b/70169921): Check param[0] has type kotlin/Function1<(renamed) B, Unit>
+    String desc = kmFunction.signature().getDesc();
+    assertThat(desc, containsString("kotlin/jvm/functions/Function1"));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java
new file mode 100644
index 0000000..ec9cdc7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java
@@ -0,0 +1,120 @@
+// Copyright (c) 2020, 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.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
+import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteInFunctionWithDefaultValueTest extends KotlinMetadataTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  public MetadataRewriteInFunctionWithDefaultValueTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  private static Map<KotlinTargetVersion, Path> defaultValueLibJarMap = new HashMap<>();
+
+  @BeforeClass
+  public static void createLibJar() throws Exception {
+    String default_valueLibFolder = PKG_PREFIX + "/default_value_lib";
+    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+      Path default_valueLibJar =
+          kotlinc(KOTLINC, targetVersion)
+              .addSourceFiles(getKotlinFileInTest(default_valueLibFolder, "lib"))
+              .compile();
+      defaultValueLibJarMap.put(targetVersion, default_valueLibJar);
+    }
+  }
+
+  @Test
+  public void testMetadataInFunctionWithDefaultValue() throws Exception {
+    Path libJar =
+        testForR8(parameters.getBackend())
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar(), ToolHelper.getKotlinStdlibJar())
+            .addProgramFiles(defaultValueLibJarMap.get(targetVersion))
+            // Keep LibKt and applyMap function, along with applyMap$default
+            .addKeepRules("-keep class **.LibKt { *** applyMap*(...); }")
+            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .compile()
+            .inspect(this::inspect)
+            .writeToZip();
+
+    ProcessResult kotlinTestCompileResult =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/default_value_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            // TODO(b/70169921): update to just .compile() once fixed.
+            .compileRaw();
+
+    // TODO(b/70169921): should be able to compile!
+    assertNotEquals(0, kotlinTestCompileResult.exitCode);
+    assertThat(
+        kotlinTestCompileResult.stderr,
+        containsString("type mismatch: inferred type is kotlin.collections.Map<String, String"));
+    assertThat(
+        kotlinTestCompileResult.stderr, containsString("but java.util.Map<K, V> was expected"));
+    assertThat(
+        kotlinTestCompileResult.stderr, containsString("no value passed for parameter 'p2'"));
+  }
+
+  private void inspect(CodeInspector inspector) {
+    String libClassName = PKG + ".default_value_lib.LibKt";
+
+    ClassSubject libKt = inspector.clazz(libClassName);
+    assertThat(libKt, isPresent());
+    assertThat(libKt, not(isRenamed()));
+
+    MethodSubject methodSubject = libKt.uniqueMethodWithName("applyMap");
+    assertThat(methodSubject, isPresent());
+    assertThat(methodSubject, not(isRenamed()));
+
+    methodSubject = libKt.uniqueMethodWithName("applyMap$default");
+    assertThat(methodSubject, isPresent());
+    assertThat(methodSubject, not(isRenamed()));
+
+    KmPackageSubject kmPackage = libKt.getKmPackage();
+    assertThat(kmPackage, isPresent());
+
+    KmFunctionSubject kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("applyMap");
+    assertThat(kmFunction, isExtensionFunction());
+    // TODO(b/70169921): inspect 2nd arg has flag that says it has a default value.
+    //  https://github.com/JetBrains/kotlin/blob/master/libraries/kotlinx-metadata/src/kotlinx/metadata/Flag.kt#L455
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
new file mode 100644
index 0000000..f4e79f3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
@@ -0,0 +1,120 @@
+// Copyright (c) 2020, 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.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
+import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteInFunctionWithVarargTest extends KotlinMetadataTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  public MetadataRewriteInFunctionWithVarargTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  private static Map<KotlinTargetVersion, Path> varargLibJarMap = new HashMap<>();
+
+  @BeforeClass
+  public static void createLibJar() throws Exception {
+    String varargLibFolder = PKG_PREFIX + "/vararg_lib";
+    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+      Path varargLibJar =
+          kotlinc(KOTLINC, targetVersion)
+              .addSourceFiles(getKotlinFileInTest(varargLibFolder, "lib"))
+              .compile();
+      varargLibJarMap.put(targetVersion, varargLibJar);
+    }
+  }
+
+  @Test
+  public void testMetadataInFunctionWithVararg() throws Exception {
+    Path libJar =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(varargLibJarMap.get(targetVersion))
+            // keep SomClass#foo, since there is a method reference in the app.
+            .addKeepRules("-keep class **.SomeClass { *** foo(...); }")
+            // Keep LibKt, along with bar function.
+            .addKeepRules("-keep class **.LibKt { *** bar(...); }")
+            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .compile()
+            .inspect(this::inspect)
+            .writeToZip();
+
+    ProcessResult kotlinTestCompileResult =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/vararg_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            // TODO(b/70169921): update to just .compile() once fixed.
+            .compileRaw();
+
+    // TODO(b/70169921): should be able to compile!
+    assertNotEquals(0, kotlinTestCompileResult.exitCode);
+    assertThat(
+        kotlinTestCompileResult.stderr,
+        containsString("type mismatch: inferred type is String but Array<T> was expected"));
+  }
+
+  private void inspect(CodeInspector inspector) {
+    String className = PKG + ".vararg_lib.SomeClass";
+    String libClassName = PKG + ".vararg_lib.LibKt";
+
+    ClassSubject cls = inspector.clazz(className);
+    assertThat(cls, isPresent());
+    assertThat(cls, not(isRenamed()));
+
+    MethodSubject foo = cls.uniqueMethodWithName("foo");
+    assertThat(foo, isPresent());
+    assertThat(foo, not(isRenamed()));
+
+    ClassSubject libKt = inspector.clazz(libClassName);
+    assertThat(libKt, isPresent());
+    assertThat(libKt, not(isRenamed()));
+
+    MethodSubject bar = libKt.uniqueMethodWithName("bar");
+    assertThat(bar, isPresent());
+    assertThat(bar, not(isRenamed()));
+
+    KmPackageSubject kmPackage = libKt.getKmPackage();
+    assertThat(kmPackage, isPresent());
+
+    KmFunctionSubject kmFunction = kmPackage.kmFunctionWithUniqueName("bar");
+    assertThat(kmFunction, not(isExtensionFunction()));
+    // TODO(b/70169921): inspect 1st arg is `vararg`.
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java
new file mode 100644
index 0000000..7ecf894
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2020, 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.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteInNestedClassTest extends KotlinMetadataTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  public MetadataRewriteInNestedClassTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  private static final Map<KotlinTargetVersion, Path> nestedLibJarMap = new HashMap<>();
+
+  @BeforeClass
+  public static void createLibJar() throws Exception {
+    String nestedLibFolder = PKG_PREFIX + "/nested_lib";
+    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+      Path nestedLibJar =
+          kotlinc(KOTLINC, targetVersion)
+              .addSourceFiles(getKotlinFileInTest(nestedLibFolder, "lib"))
+              .compile();
+      nestedLibJarMap.put(targetVersion, nestedLibJar);
+    }
+  }
+
+  @Test
+  public void testMetadataInNestedClass() throws Exception {
+    Path libJar =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(nestedLibJarMap.get(targetVersion))
+            // Keep the Outer class and delegations.
+            .addKeepRules("-keep class **.Outer { <init>(...); *** delegate*(...); }")
+            // Keep Inner to check the hierarchy.
+            .addKeepRules("-keep class **.*Inner")
+            // Keep Nested, but allow obfuscation.
+            .addKeepRules("-keep,allowobfuscation class **.*Nested")
+            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
+            .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
+            .compile()
+            .inspect(this::inspect)
+            .writeToZip();
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/nested_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".nested_app.MainKt")
+        .assertSuccessWithOutputLines("Inner::inner", "42", "Nested::nested", "42");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    String outerClassName = PKG + ".nested_lib.Outer";
+    String innerClassName = outerClassName + "$Inner";
+    String nestedClassName = outerClassName + "$Nested";
+
+    ClassSubject inner = inspector.clazz(innerClassName);
+    assertThat(inner, isPresent());
+    assertThat(inner, not(isRenamed()));
+
+    ClassSubject nested = inspector.clazz(nestedClassName);
+    assertThat(nested, isRenamed());
+
+    ClassSubject outer = inspector.clazz(outerClassName);
+    assertThat(outer, isPresent());
+    assertThat(outer, not(isRenamed()));
+
+    KmClassSubject kmClass = outer.getKmClass();
+    assertThat(kmClass, isPresent());
+
+    assertFalse(kmClass.getNestedClassDescriptors().isEmpty());
+    kmClass.getNestedClassDescriptors().forEach(nestedClassDescriptor -> {
+      ClassSubject nestedClass = inspector.clazz(descriptorToJavaType(nestedClassDescriptor));
+      if (nestedClass.getOriginalName().contains("Inner")) {
+        assertThat(nestedClass, not(isRenamed()));
+      } else {
+        assertThat(nestedClass, isRenamed());
+      }
+      assertEquals(nestedClassDescriptor, nestedClass.getFinalDescriptor());
+    });
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java
new file mode 100644
index 0000000..6bfafc4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java
@@ -0,0 +1,138 @@
+// Copyright (c) 2020, 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.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKotlinClassifier;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteInRenamedTypeTest extends KotlinMetadataTestBase {
+  private static final String OBFUSCATE_RENAMED = "-keep,allowobfuscation class **.Renamed { *; }";
+  private static final String KEEP_KEPT = "-keep class **.Kept { *; }";
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), KotlinTargetVersion.values());
+  }
+
+  public MetadataRewriteInRenamedTypeTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  private static final Map<KotlinTargetVersion, Path> annoJarMap = new HashMap<>();
+  private static final Map<KotlinTargetVersion, Path> inputJarMap = new HashMap<>();
+
+  @BeforeClass
+  public static void createInputJar() throws Exception {
+    String inputFolder = PKG_PREFIX + "/anno";
+    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+      Path annoJar =
+          kotlinc(KOTLINC, targetVersion)
+              .addSourceFiles(getKotlinFileInTest(inputFolder, "Anno"))
+              .compile();
+      Path inputJar =
+          kotlinc(KOTLINC, targetVersion)
+              .addClasspathFiles(annoJar)
+              .addSourceFiles(getKotlinFileInTest(inputFolder, "main"))
+              .compile();
+      annoJarMap.put(targetVersion, annoJar);
+      inputJarMap.put(targetVersion, inputJar);
+    }
+  }
+
+  @Test
+  public void testR8_kotlinStdlibAsLib() throws Exception {
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(
+            annoJarMap.get(targetVersion),
+            ToolHelper.getJava8RuntimeJar(),
+            ToolHelper.getKotlinStdlibJar())
+        .addProgramFiles(inputJarMap.get(targetVersion))
+        .addKeepRules(OBFUSCATE_RENAMED, KEEP_KEPT)
+        .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .compile()
+        .inspect(this::inspect);
+  }
+
+  @Test
+  public void testR8_kotlinStdlibAsClassPath() throws Exception {
+    testForR8(parameters.getBackend())
+        .addClasspathFiles(annoJarMap.get(targetVersion), ToolHelper.getKotlinStdlibJar())
+        .addProgramFiles(inputJarMap.get(targetVersion))
+        .addKeepRules(OBFUSCATE_RENAMED, KEEP_KEPT)
+        .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .compile()
+        .inspect(this::inspect);
+  }
+
+  @Test
+  public void testR8_kotlinStdlibAsProgramFile() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(annoJarMap.get(targetVersion), ToolHelper.getKotlinStdlibJar())
+        .addProgramFiles(inputJarMap.get(targetVersion))
+        .addKeepRules(OBFUSCATE_RENAMED, KEEP_KEPT)
+        .addKeepRules("-keep class **.Anno")
+        .addKeepRules("-keep class kotlin.Metadata")
+        .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .allowDiagnosticWarningMessages()
+        .compile()
+        .assertWarningMessageThatMatches(
+            equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    String pkg = getClass().getPackage().getName();
+    ClassSubject kept = inspector.clazz(pkg + ".anno.Kept");
+    assertThat(kept, isPresent());
+    assertThat(kept, not(isRenamed()));
+    // API entry is kept, hence @Metadata exists.
+    KmClassSubject kmClass = kept.getKmClass();
+    assertThat(kmClass, isPresent());
+    assertEquals(kept.getFinalDescriptor(), getDescriptorFromKotlinClassifier(kmClass.getName()));
+    // @Anno is kept.
+    String annoName = pkg + ".anno.Anno";
+    AnnotationSubject anno = kept.annotation(annoName);
+    assertThat(anno, isPresent());
+
+    ClassSubject renamed = inspector.clazz(pkg + ".anno.Renamed");
+    assertThat(renamed, isRenamed());
+    // @Anno is kept.
+    anno = renamed.annotation(annoName);
+    assertThat(anno, isPresent());
+    // @Metadata is kept even though the class is renamed.
+    kmClass = renamed.getKmClass();
+    assertThat(kmClass, isPresent());
+    assertEquals(
+        renamed.getFinalDescriptor(), getDescriptorFromKotlinClassifier(kmClass.getName()));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
index 6cd1034..b643c4b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
@@ -12,6 +12,7 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -110,6 +111,7 @@
     KmClassSubject kmClass = expr.getKmClass();
     assertThat(kmClass, isPresent());
 
+    assertFalse(kmClass.getSealedSubclassDescriptors().isEmpty());
     kmClass.getSealedSubclassDescriptors().forEach(sealedSubclassDescriptor -> {
       ClassSubject sealedSubclass =
           inspector.clazz(descriptorToJavaType(sealedSubclassDescriptor));
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index f93179f..31cd07d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -52,6 +52,8 @@
             .addKeepMainRule(mainClassName)
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
             .addKeepRules("-keep class kotlin.Metadata")
+            // TODO(b/70169921): if this option is settled down, this test is meaningless.
+            .addOptionsModification(o -> o.enableKotlinMetadataRewritingForRenamedClasses = false)
             .allowDiagnosticWarningMessages()
             .setMinApi(parameters.getApiLevel())
             .compile()
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/anno/Anno.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/anno/Anno.kt
new file mode 100644
index 0000000..3e37cc4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/anno/Anno.kt
@@ -0,0 +1,7 @@
+// Copyright (c) 2020, 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.metadata.anno
+
+@Retention(AnnotationRetention.RUNTIME)
+annotation class Anno
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/anno/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/anno/main.kt
new file mode 100644
index 0000000..c2443e5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/anno/main.kt
@@ -0,0 +1,14 @@
+// Copyright (c) 2020, 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.metadata.anno
+
+@Anno
+class Renamed {
+  var num: Int = 8
+}
+
+@Anno
+class Kept {
+  val answer: Int = 42
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_app/main.kt
index 55c61e3..403021e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_app/main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_app/main.kt
@@ -7,6 +7,7 @@
 
 fun main() {
   B().doStuff()
-  B.singleton.doStuff()
+  B.elt1.doStuff()
+  B.elt2.doStuff()
   println(B.foo)
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt
index 9e9f2eb..e3da633 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt
@@ -19,7 +19,9 @@
   }
 
   companion object {
-    val singleton: Super = B()
+    val elt1: Super = B()
+    @JvmField
+    val elt2: Super = B()
     val foo: String
       get() = "B.Companion:foo"
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/default_value_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/default_value_app/main.kt
new file mode 100644
index 0000000..9cb6039
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/default_value_app/main.kt
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, 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.metadata.default_value_app
+
+import com.android.tools.r8.kotlin.metadata.default_value_lib.applyMap
+
+fun main() {
+  val m = mapOf("A" to "a", "B" to "b", "C" to "c")
+  val s = listOf("A", "B", "C").joinToString(separator = "\n")
+  println(s.applyMap(m))
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/default_value_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/default_value_lib/lib.kt
new file mode 100644
index 0000000..c76b5be
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/default_value_lib/lib.kt
@@ -0,0 +1,9 @@
+// Copyright (c) 2020, 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.metadata.default_value_lib
+
+fun String.applyMap(map: Map<String, String>, separator: String = "\n") =
+  this.split(separator).joinToString(separator = separator) {
+    if (map.containsKey(it)) map.getValue(it) else it
+  }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_app/main.kt
index 2196586..d85c992 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_app/main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_app/main.kt
@@ -5,8 +5,16 @@
 
 import com.android.tools.r8.kotlin.metadata.extension_function_lib.B
 import com.android.tools.r8.kotlin.metadata.extension_function_lib.extension
+import com.android.tools.r8.kotlin.metadata.extension_function_lib.csHash
+import com.android.tools.r8.kotlin.metadata.extension_function_lib.longArrayHash
+import com.android.tools.r8.kotlin.metadata.extension_function_lib.myApply
 
 fun main() {
   B().doStuff()
   B().extension()
+
+  "R8".csHash()
+  longArrayOf(42L).longArrayHash()
+  // TODO(b/70169921): Need to set arguments as type parameter.
+  //  B().myApply { this.doStuff() }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_lib/B.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_lib/B.kt
index 84c759a..4321ac1 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_lib/B.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_lib/B.kt
@@ -17,4 +17,20 @@
 
 fun B.extension() {
   doStuff()
-}
\ No newline at end of file
+}
+
+fun CharSequence.csHash(): Long {
+  var result = 0L
+  this.forEach { result = result * 8L + it.toLong() }
+  return result
+}
+
+fun LongArray.longArrayHash(): Long {
+  var result = 0L
+  this.forEach { result = result * 8L + it }
+  return result
+}
+
+fun B.myApply(apply: B.() -> Unit) {
+  apply.invoke(this)
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/nested_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/nested_app/main.kt
new file mode 100644
index 0000000..d4f91d4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/nested_app/main.kt
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, 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.metadata.nested_app
+
+import com.android.tools.r8.kotlin.metadata.nested_lib.Outer
+
+fun main() {
+  val o = Outer()
+  println(o.delegateInner())
+  println(o.delegateNested(21))
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/nested_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/nested_lib/lib.kt
new file mode 100644
index 0000000..13765fc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/nested_lib/lib.kt
@@ -0,0 +1,27 @@
+// Copyright (c) 2020, 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.metadata.nested_lib
+
+class Outer {
+  val prop1
+    get() = 42
+
+  fun delegateInner() = Inner().inner()
+
+  fun delegateNested(x: Int) = Nested().nested(x)
+
+  inner class Inner {
+    fun inner(): Int {
+      println("Inner::inner")
+      return this@Outer.prop1
+    }
+  }
+
+  class Nested {
+    fun nested(x: Int): Int {
+      println("Nested::nested")
+      return 2 * x
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/vararg_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/vararg_app/main.kt
new file mode 100644
index 0000000..417bfcd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/vararg_app/main.kt
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, 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.metadata.vararg_app
+
+import com.android.tools.r8.kotlin.metadata.vararg_lib.bar
+
+fun main() {
+  bar("R8") { instance, str ->
+    instance.foo(str)
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/vararg_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/vararg_lib/lib.kt
new file mode 100644
index 0000000..8cc6e19
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/vararg_lib/lib.kt
@@ -0,0 +1,19 @@
+// Copyright (c) 2020, 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.metadata.vararg_lib
+
+class SomeClass {
+  fun foo(x : String) {
+    println("SomeClass::$x")
+  }
+}
+
+fun bar(vararg strs: String, f: (SomeClass, String) -> Unit) {
+  if (strs.isNotEmpty() && strs.any { it.startsWith("R8") }) {
+    val instance = SomeClass()
+    strs
+      .filter { it.startsWith("R8") }
+      .map { f.invoke(instance, it) }
+  }
+}
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 8bc0e79..d1ecf46 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -9,10 +9,18 @@
 import static com.android.tools.r8.utils.FileUtils.withNativeFileSeparators;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import com.android.tools.r8.GenerateMainDexList;
 import com.android.tools.r8.GenerateMainDexListCommand;
+import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.references.Reference;
@@ -21,7 +29,6 @@
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -35,11 +42,9 @@
 import java.util.Enumeration;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
-import org.junit.Assert;
 import org.junit.Test;
 
 public class MainDexTracingTest extends TestBase {
@@ -61,9 +66,10 @@
         Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules-whyareyoukeeping.txt"),
         Paths.get(EXAMPLE_SRC_DIR, "multidex001", "ref-list-1.txt"),
         Paths.get(EXAMPLE_SRC_DIR, "multidex001", "ref-list-1.txt"),
-        AndroidApiLevel.I);
+        AndroidApiLevel.I,
+        TestCompilerBuilder::allowStdoutMessages);
     String output = new String(baos.toByteArray(), Charset.defaultCharset());
-    Assert.assertThat(output, containsString("is referenced in keep rule:"));
+    assertThat(output, containsString("is referenced in keep rule:"));
     System.setOut(stdout);
   }
 
@@ -78,10 +84,12 @@
         Paths.get(EXAMPLE_SRC_DIR, "multidex001", "ref-list-1.txt"),
         Paths.get(EXAMPLE_SRC_DIR, "multidex001", "ref-list-1.txt"),
         AndroidApiLevel.I,
-        options -> {
-          options.enableInlining = false;
-          options.mainDexKeptGraphConsumer = graphConsumer;
-        });
+        builder ->
+            builder.addOptionsModification(
+                options -> {
+                  options.enableInlining = false;
+                  options.mainDexKeptGraphConsumer = graphConsumer;
+                }));
     {
       ByteArrayOutputStream baos = new ByteArrayOutputStream();
       graphConsumer.printWhyAreYouKeeping(
@@ -92,7 +100,7 @@
               "multidex001.MainActivity",
               "|- is referenced in keep rule:",
               withNativeFileSeparators("|  src/test/examples/multidex/main-dex-rules.txt:14:1"));
-      Assert.assertEquals(expected, output);
+      assertEquals(expected, output);
     }
     {
       ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -110,7 +118,7 @@
                   "|- is referenced in keep rule:",
                   withNativeFileSeparators(
                       "|  src/test/examples/multidex/main-dex-rules.txt:14:1"));
-      Assert.assertEquals(expected, output);
+      assertEquals(expected, output);
     }
   }
 
@@ -271,9 +279,7 @@
         expectedR8MainDexList,
         expectedMainDexList,
         minSdk,
-        (options) -> {
-          options.enableInlining = false;
-        });
+        builder -> builder.addOptionsModification(options -> options.enableInlining = false));
   }
 
   private void doTest(
@@ -284,7 +290,7 @@
       Path expectedR8MainDexList,
       Path expectedMainDexList,
       AndroidApiLevel minSdk,
-      Consumer<InternalOptions> optionsConsumer)
+      ThrowableConsumer<R8FullTestBuilder> configuration)
       throws Throwable {
     Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
 
@@ -328,7 +334,7 @@
         .addProgramFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
         .addKeepRules("-keepattributes *Annotation*")
         .addMainDexRuleFiles(mainDexRules)
-        .addOptionsModification(optionsConsumer)
+        .apply(configuration)
         .allowDiagnosticWarningMessages()
         .assumeAllMethodsMayHaveSideEffects()
         .setMinApi(minSdk)
@@ -351,7 +357,7 @@
     for (int i = 0; i < r8RefList.length; i++) {
       String reference = r8RefList[i].trim();
       if (r8MainDexList.size() <= i) {
-        Assert.fail("R8 main dex list is missing '" + reference + "'");
+        fail("R8 main dex list is missing '" + reference + "'");
       }
       checkSameMainDexEntry(reference, r8MainDexList.get(i));
     }
@@ -363,7 +369,7 @@
       // The main dex list generator does not do any lambda desugaring.
       if (!isLambda(reference)) {
         if (mainDexGeneratorMainDexList.size() <= i - nonLambdaOffset) {
-          Assert.fail("Main dex list generator is missing '" + reference + "'");
+          fail("Main dex list generator is missing '" + reference + "'");
         }
         checkSameMainDexEntry(reference, mainDexGeneratorMainDexList.get(i - nonLambdaOffset));
         checkSameMainDexEntry(
@@ -389,9 +395,9 @@
         continue;
       }
       if (index == 0) {
-        Assert.assertEquals("classes.dex", entry.getName());
+        assertEquals("classes.dex", entry.getName());
       } else {
-        Assert.assertEquals("classes" + (index + 1) + ".dex", entry.getName());
+        assertEquals("classes" + (index + 1) + ".dex", entry.getName());
       }
       index++;
     }
@@ -399,7 +405,7 @@
     String[] entriesUnsorted = entryNames.toArray(StringUtils.EMPTY_ARRAY);
     String[] entriesSorted = entryNames.toArray(StringUtils.EMPTY_ARRAY);
     Arrays.sort(entriesSorted);
-    Assert.assertArrayEquals(entriesUnsorted, entriesSorted);
+    assertArrayEquals(entriesUnsorted, entriesSorted);
   }
 
   private boolean isLambda(String mainDexEntry) {
@@ -407,7 +413,7 @@
   }
 
   private String mainDexStringToDescriptor(String mainDexString) {
-    Assert.assertTrue(mainDexString.endsWith(FileUtils.CLASS_EXTENSION));
+    assertTrue(mainDexString.endsWith(FileUtils.CLASS_EXTENSION));
     return DescriptorUtils.getDescriptorFromClassBinaryName(
         mainDexString.substring(0, mainDexString.length() - FileUtils.CLASS_EXTENSION.length()));
   }
@@ -421,6 +427,6 @@
       reference = reference.substring(0, reference.lastIndexOf('$'));
       computed = computed.substring(0, computed.lastIndexOf('$'));
     }
-    Assert.assertEquals(reference, computed);
+    assertEquals(reference, computed);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java b/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java
index cd80843..c4a616c 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java
@@ -90,14 +90,15 @@
             .setMinApi(AndroidApiLevel.K)
             .addProgramClasses(CLASSES)
             .addMainDexRules(keepMainProguardConfiguration(HelloWorldMain.class))
-            .setMainDexKeptGraphConsumer(consumer);
+            .setMainDexKeptGraphConsumer(consumer)
+            .allowStdoutMessages();
     if (rule != null) {
       builder.addMainDexRules(rule);
     }
     builder.compile();
   }
 
-  private String runTest(Class clazz) throws Exception {
+  private String runTest(Class<?> clazz) throws Exception {
     PrintStream stdout = System.out;
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     String rule = null;
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index b03aeaa..f0b3c24 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -8,8 +8,8 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerFactory;
 import com.android.tools.r8.shaking.ProguardConfiguration;
@@ -41,7 +41,7 @@
 
   private final Timing timing;
 
-  private DexApplication program;
+  private DirectMappedDexApplication program;
   protected DexItemFactory dexItemFactory;
 
   protected NamingTestBase(
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/UnrelatedClasspathClassFieldAccessTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/UnrelatedClasspathClassFieldAccessTest.java
new file mode 100644
index 0000000..4cb3b94
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/UnrelatedClasspathClassFieldAccessTest.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2020, 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.applymapping;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class UnrelatedClasspathClassFieldAccessTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public UnrelatedClasspathClassFieldAccessTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public static class ClasspathClass {
+    public static int field = 42;
+  }
+
+  public static class ProgramClass {
+
+    public static void main(String[] args) {
+      System.out.println(ClasspathClass.field);
+    }
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(ProgramClass.class, ClasspathClass.class)
+        .run(parameters.getRuntime(), ProgramClass.class)
+        .assertSuccessWithOutputLines("42");
+  }
+
+  @Test
+  public void testApplyMapping() throws Exception {
+    R8TestCompileResult classpath =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(ClasspathClass.class)
+            .addKeepAllClassesRuleWithAllowObfuscation()
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+
+    testForR8(parameters.getBackend())
+        .addProgramClasses(ProgramClass.class)
+        .addClasspathClasses(ClasspathClass.class)
+        .addApplyMapping(classpath.getProguardMap())
+        .addKeepMainRule(ProgramClass.class)
+        .addRunClasspathFiles(classpath.writeToZip())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), ProgramClass.class)
+        .assertSuccessWithOutputLines("42");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/UnrelatedClasspathClassIndirectFieldAccessTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/UnrelatedClasspathClassIndirectFieldAccessTest.java
new file mode 100644
index 0000000..f9397d8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/UnrelatedClasspathClassIndirectFieldAccessTest.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2020, 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.applymapping;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class UnrelatedClasspathClassIndirectFieldAccessTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public UnrelatedClasspathClassIndirectFieldAccessTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public static class ClasspathClassA {
+    public static int field = 42;
+  }
+
+  public static class ClasspathClassB extends ClasspathClassA {}
+
+  public static class ProgramClass {
+
+    public static void main(String[] args) {
+      System.out.println(ClasspathClassB.field);
+    }
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(ProgramClass.class, ClasspathClassA.class, ClasspathClassB.class)
+        .run(parameters.getRuntime(), ProgramClass.class)
+        .assertSuccessWithOutputLines("42");
+  }
+
+  @Test
+  public void testApplyMapping() throws Exception {
+    R8TestCompileResult classpath =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(ClasspathClassA.class, ClasspathClassB.class)
+            .addKeepAllClassesRuleWithAllowObfuscation()
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+
+    testForR8(parameters.getBackend())
+        .addProgramClasses(ProgramClass.class)
+        .addClasspathClasses(ClasspathClassA.class, ClasspathClassB.class)
+        .addApplyMapping(classpath.getProguardMap())
+        .addKeepMainRule(ProgramClass.class)
+        .addRunClasspathFiles(classpath.writeToZip())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), ProgramClass.class)
+        .assertSuccessWithOutputLines("42");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/b130791310/B130791310.java b/src/test/java/com/android/tools/r8/naming/b130791310/B130791310.java
index b12b05f..d8f3910 100644
--- a/src/test/java/com/android/tools/r8/naming/b130791310/B130791310.java
+++ b/src/test/java/com/android/tools/r8/naming/b130791310/B130791310.java
@@ -8,12 +8,11 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.ProguardTestBuilder;
-import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -92,15 +91,21 @@
 
   private final boolean enableClassMerging;
   private final boolean onlyForceInlining;
+  private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "enable class merging: {0}, only force inlining: {1}")
+  @Parameterized.Parameters(name = "{2}, enable class merging: {0}, only force inlining: {1}")
   public static List<Object[]> data() {
-    return buildParameters(BooleanUtils.values(), BooleanUtils.values());
+    return buildParameters(
+        BooleanUtils.values(),
+        BooleanUtils.values(),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
-  public B130791310(boolean enableClassMerging, boolean onlyForceInlining) {
+  public B130791310(
+      boolean enableClassMerging, boolean onlyForceInlining, TestParameters parameters) {
     this.enableClassMerging = enableClassMerging;
     this.onlyForceInlining = onlyForceInlining;
+    this.parameters = parameters;
   }
 
   private void inspect(CodeInspector inspector, boolean isR8) {
@@ -130,38 +135,39 @@
   @Test
   public void testProguard() throws Exception {
     assumeFalse(onlyForceInlining);
-    ProguardTestBuilder builder =
-        testForProguard()
-            .addProgramClasses(CLASSES)
-            .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
-            .addKeepClassAndMembersRules(MAIN)
-            .addKeepRules(RULES)
-            .addTestingAnnotationsAsProgramClasses();
-    if (!enableClassMerging) {
-        builder.addKeepRules("-optimizations !class/merging/*");
-    }
-    builder
+    assumeTrue(parameters.isCfRuntime());
+    testForProguard()
+        .addProgramClasses(CLASSES)
+        .addKeepClassAndMembersRules(MAIN)
+        .addKeepRules(RULES)
+        .addTestingAnnotationsAsProgramClasses()
+        .setMinApi(parameters.getApiLevel())
+        .apply(
+            builder -> {
+              if (!enableClassMerging) {
+                builder.addKeepRules("-optimizations !class/merging/*");
+              }
+            })
         .compile()
         .inspect(inspector -> inspect(inspector, false));
   }
 
   @Test
   public void testR8() throws Exception {
-    R8FullTestBuilder builder =
-        testForR8(Backend.DEX)
-            .addProgramClasses(CLASSES)
-            .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
-            .addKeepClassAndMembersRules(MAIN)
-            .addKeepRules(RULES)
-            .enableNeverClassInliningAnnotations();
-    if (!enableClassMerging) {
-      builder.addOptionsModification(o -> o.enableVerticalClassMerging = false);
-    }
-    if (onlyForceInlining) {
-      builder.addOptionsModification(
-          o -> o.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE));
-    }
-    builder
+    testForR8(parameters.getBackend())
+        .addProgramClasses(CLASSES)
+        .addKeepClassAndMembersRules(MAIN)
+        .addKeepRules(RULES)
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(o -> o.enableVerticalClassMerging = enableClassMerging)
+        .apply(
+            builder -> {
+              if (onlyForceInlining) {
+                builder.addOptionsModification(
+                    o -> o.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE));
+              }
+            })
         .compile()
         .inspect(inspector -> inspect(inspector, true));
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
index c7f8ef9..8034ad7 100644
--- a/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
@@ -11,10 +11,10 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -36,7 +36,8 @@
             .addLibraryFile(ToolHelper.getDefaultAndroidJar())
             .addProgramFiles(ToolHelper.getClassFileForTestClass(Foo.class))
             .build();
-    DexApplication application = new ApplicationReader(app, options, timing).read().toDirect();
+    DirectMappedDexApplication application =
+        new ApplicationReader(app, options, timing).read().toDirect();
     AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
     DexItemFactory factory = options.itemFactory;
     DexType fooType =
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index b473a40..45a9a20 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -222,7 +222,7 @@
         appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).getSingleTarget());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     if (resolutionResult.isVirtualTarget()) {
-      Set<DexEncodedMethod> targets = resolutionResult.lookupVirtualTargets(appInfo);
+      Set<DexEncodedMethod> targets = resolutionResult.lookupVirtualDispatchTargets(appInfo);
       Set<DexType> targetHolders =
           targets.stream().map(m -> m.method.holder).collect(Collectors.toSet());
       Assert.assertEquals(allTargetHolders.size(), targetHolders.size());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
index 609b506..b119874 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
@@ -55,7 +55,7 @@
     DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
     Set<String> targets =
-        resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+        resolutionResult.lookupVirtualDispatchTargets(appInfo.withSubtyping()).stream()
             .map(DexEncodedMethod::qualifiedName)
             .collect(Collectors.toSet());
     ImmutableSet<String> expected =
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
index 37f346b..f926d4b 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
@@ -53,7 +53,7 @@
     DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
     Set<String> targets =
-        resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+        resolutionResult.lookupVirtualDispatchTargets(appInfo.withSubtyping()).stream()
             .map(DexEncodedMethod::qualifiedName)
             .collect(Collectors.toSet());
     ImmutableSet<String> expected =
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
new file mode 100644
index 0000000..61e872e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
@@ -0,0 +1,160 @@
+// Copyright (c) 2020, 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.resolution.interfacetargets;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+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 DefaultWithoutTopTest extends TestBase {
+
+  private static final String[] EXPECTED = new String[] {"J.foo"};
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public DefaultWithoutTopTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testDynamicLookupTargets() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppInfoWithLiveness appInfo =
+        computeAppViewWithLiveness(
+                buildClasses(I.class, J.class, Main.class)
+                    .addClassProgramData(setAImplementsIAndJ())
+                    .build(),
+                Main.class)
+            .appInfo();
+    DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
+    Set<String> targets =
+        appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appInfo).stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addProgramClasses(I.class, J.class, Main.class)
+        .addProgramClassFileData(setAImplementsIAndJ())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class, J.class, Main.class)
+        .addProgramClassFileData(setAImplementsIAndJ())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testtestDynamicLookupTargetsWithIndirectDefault() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppInfoWithLiveness appInfo =
+        computeAppViewWithLiveness(
+                buildClasses(I.class, J.class, K.class, Main.class)
+                    .addClassProgramData(setAimplementsIandK())
+                    .build(),
+                Main.class)
+            .appInfo();
+    DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
+    Set<String> targets =
+        appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appInfo).stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+  }
+
+  @Test
+  public void testRuntimeWithIndirectDefault()
+      throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addProgramClasses(I.class, J.class, K.class, Main.class)
+        .addProgramClassFileData(setAimplementsIandK())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8WithIndirectDefault()
+      throws IOException, CompilationFailedException, ExecutionException {
+    // TODO(b/148686556): Fix test expectation.
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class, J.class, K.class, Main.class)
+        .addProgramClassFileData(setAimplementsIandK())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(containsString("java.lang.AbstractMethodError"));
+  }
+
+  private byte[] setAImplementsIAndJ() throws IOException {
+    return transformer(A.class).setImplements(I.class, J.class).transform();
+  }
+
+  private byte[] setAimplementsIandK() throws IOException {
+    return transformer(A.class).setImplements(I.class, K.class).transform();
+  }
+
+  @NeverMerge
+  public interface I {
+    void foo();
+  }
+
+  @NeverMerge
+  public interface J {
+    @NeverInline
+    default void foo() {
+      System.out.println("J.foo");
+    }
+  }
+
+  public interface K extends J {}
+
+  @NeverClassInline
+  public static class A implements J /* I, J or I, K */ {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      ((I) new A()).foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
index 65448a0..a69a18b 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
@@ -60,7 +60,7 @@
     Assert.assertThrows(
         AssertionError.class,
         () -> {
-          appInfo.resolveMethod(method.holder, method).lookupInterfaceTargets(appInfo);
+          appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appInfo);
         });
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
index ede5785..3b6022b 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
@@ -50,7 +50,7 @@
     DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
     Assert.assertThrows(
         AssertionError.class,
-        () -> appInfo.resolveMethod(method.holder, method).lookupInterfaceTargets(appInfo));
+        () -> appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appInfo));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
index 3a455cc..88fd988 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
@@ -54,7 +54,7 @@
     DexMethod method = buildNullaryVoidMethod(J.class, "bar", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
     Set<String> targets =
-        resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+        resolutionResult.lookupVirtualDispatchTargets(appInfo.withSubtyping()).stream()
             .map(DexEncodedMethod::qualifiedName)
             .collect(Collectors.toSet());
     ImmutableSet<String> expected =
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
index 8b96a84..23c2215 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
@@ -54,7 +54,7 @@
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
     Set<String> targets =
-        resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+        resolutionResult.lookupVirtualDispatchTargets(appInfo.withSubtyping()).stream()
             .map(DexEncodedMethod::qualifiedName)
             .collect(Collectors.toSet());
     ImmutableSet<String> expected =
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
index 60ed217..03a6141 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
@@ -55,7 +55,7 @@
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
     Set<String> targets =
-        resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+        resolutionResult.lookupVirtualDispatchTargets(appInfo.withSubtyping()).stream()
             .map(DexEncodedMethod::qualifiedName)
             .collect(Collectors.toSet());
     ImmutableSet<String> expected =
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
index 4986840..e73a10e 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
@@ -54,7 +54,7 @@
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
     Set<String> targets =
-        resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+        resolutionResult.lookupVirtualDispatchTargets(appInfo.withSubtyping()).stream()
             .map(DexEncodedMethod::qualifiedName)
             .collect(Collectors.toSet());
     ImmutableSet<String> expected =
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
index 3ccb04b..24916b0 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
@@ -53,7 +53,7 @@
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
     Set<String> targets =
-        resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+        resolutionResult.lookupVirtualDispatchTargets(appInfo.withSubtyping()).stream()
             .map(DexEncodedMethod::qualifiedName)
             .collect(Collectors.toSet());
     ImmutableSet<String> expected =
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
index 4e4a494..a9b34f2 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
@@ -53,7 +53,7 @@
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
     Set<String> targets =
-        resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+        resolutionResult.lookupVirtualDispatchTargets(appInfo.withSubtyping()).stream()
             .map(DexEncodedMethod::qualifiedName)
             .collect(Collectors.toSet());
     ImmutableSet<String> expected =
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
index 83831d1..8a52035 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
@@ -52,7 +52,7 @@
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
     Set<String> targets =
-        appInfo.resolveMethod(method.holder, method).lookupVirtualTargets(appInfo).stream()
+        appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appInfo).stream()
             .map(DexEncodedMethod::qualifiedName)
             .collect(Collectors.toSet());
     // TODO(b/148591377): Should we report B.foo()?
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
index 988f1a3..34b536b 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
@@ -52,7 +52,7 @@
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
     Set<String> targets =
-        appInfo.resolveMethod(method.holder, method).lookupVirtualTargets(appInfo).stream()
+        appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appInfo).stream()
             .map(DexEncodedMethod::qualifiedName)
             .collect(Collectors.toSet());
     // TODO(b/148591377): I.foo() should ideally not be included in the set.
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
index 89f17da..399ad7d 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
@@ -52,7 +52,7 @@
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
     Set<String> targets =
-        appInfo.resolveMethod(method.holder, method).lookupVirtualTargets(appInfo).stream()
+        appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appInfo).stream()
             .map(DexEncodedMethod::qualifiedName)
             .collect(Collectors.toSet());
     ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
new file mode 100644
index 0000000..4759dc3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2020, 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.resolution.virtualtargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+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 DefaultWithoutTopTest extends TestBase {
+
+  private static final String[] EXPECTED = new String[] {"J.foo"};
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public DefaultWithoutTopTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testDynamicLookupTargets() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppInfoWithLiveness appInfo =
+        computeAppViewWithLiveness(
+                buildClasses(I.class, J.class, Main.class)
+                    .addClassProgramData(setAImplementsIAndJ())
+                    .build(),
+                Main.class)
+            .appInfo();
+    DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
+    Set<String> targets =
+        appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appInfo).stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addProgramClasses(I.class, J.class, Main.class)
+        .addProgramClassFileData(setAImplementsIAndJ())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class, J.class, Main.class)
+        .addProgramClassFileData(setAImplementsIAndJ())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  private byte[] setAImplementsIAndJ() throws IOException {
+    return transformer(A.class).setImplements(I.class, J.class).transform();
+  }
+
+  @NeverMerge
+  public interface I {
+    void foo();
+  }
+
+  @NeverMerge
+  public interface J {
+    @NeverInline
+    default void foo() {
+      System.out.println("J.foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class A implements J /* I,J */ {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new A().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
index 2f979d9..ebbfcef 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
@@ -51,7 +51,7 @@
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
     Set<String> targets =
-        appInfo.resolveMethod(method.holder, method).lookupVirtualTargets(appInfo).stream()
+        appInfo.resolveMethod(method.holder, method).lookupVirtualDispatchTargets(appInfo).stream()
             .map(DexEncodedMethod::qualifiedName)
             .collect(Collectors.toSet());
     ImmutableSet<String> expected = ImmutableSet.of(I.class.getTypeName() + ".foo");
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
index 5bef2ca..e4d1f55 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
@@ -54,7 +54,7 @@
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     Set<String> targets =
-        resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+        resolutionResult.lookupVirtualDispatchTargets(appInfo.withSubtyping()).stream()
             .map(DexEncodedMethod::qualifiedName)
             .collect(Collectors.toSet());
     ImmutableSet<String> expected =
diff --git a/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java b/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java
index c46d3ce..809a521 100644
--- a/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java
@@ -16,9 +16,12 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.Objects;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -120,7 +123,7 @@
                 assertThat(
                     stackTrace,
                     isSameExceptForFileNameAndLineNumber(
-                        StackTrace.builder()
+                        createStackTraceBuilder()
                             .addWithoutFileNameAndLineNumber(
                                 Result.class, "methodWhichAccessInstanceMethod")
                             .addWithoutFileNameAndLineNumber(
@@ -148,7 +151,7 @@
                 assertThat(
                     stackTrace,
                     isSameExceptForFileNameAndLineNumber(
-                        StackTrace.builder()
+                        createStackTraceBuilder()
                             .addWithoutFileNameAndLineNumber(
                                 Result.class, "methodWhichAccessInstanceField")
                             .addWithoutFileNameAndLineNumber(
@@ -178,7 +181,7 @@
                 assertThat(
                     stackTrace,
                     isSameExceptForFileNameAndLineNumber(
-                        StackTrace.builder()
+                        createStackTraceBuilder()
                             .addWithoutFileNameAndLineNumber(
                                 Result.class, "methodWhichAccessStaticField")
                             .addWithoutFileNameAndLineNumber(
@@ -188,6 +191,24 @@
                             .build())));
   }
 
+  private StackTrace.Builder createStackTraceBuilder() {
+    StackTrace.Builder builder = StackTrace.builder();
+    if (canUseRequireNonNull()) {
+      if (parameters.isCfRuntime()
+          && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK9)) {
+        builder.addWithoutFileNameAndLineNumber("java.base/java.util.Objects", "requireNonNull");
+      } else {
+        builder.addWithoutFileNameAndLineNumber(Objects.class, "requireNonNull");
+      }
+    }
+    return builder;
+  }
+
+  private boolean canUseRequireNonNull() {
+    return parameters.isCfRuntime()
+        || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K);
+  }
+
   static class TestClassForInlineMethod {
 
     public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
index 13190d9..d70f22b 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
@@ -122,7 +122,7 @@
             .streamInstructions()
             .filter(InstructionSubject::isThrow)
             .collect(toSingle())
-            .retracePosition(codeInspector.retrace());
+            .retraceLinePosition(codeInspector.retrace());
     assertThat(retraceResult, isInlineFrame());
     assertThat(retraceResult, isInlineStack(inlineStack));
     assertThat(stackTrace, containsInlinePosition(inlineStack));
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
index 2d9f47b..b4d302b 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.Matchers.InlinePosition;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -64,6 +65,20 @@
         .compile();
   }
 
+  private FoundMethodSubject inlineExceptionStatic(CodeInspector kotlinInspector) {
+    return kotlinInspector
+        .clazz("retrace.InlineFunctionKt")
+        .uniqueMethodWithName("inlineExceptionStatic")
+        .asFoundMethodSubject();
+  }
+
+  private FoundMethodSubject inlineExceptionInstance(CodeInspector kotlinInspector) {
+    return kotlinInspector
+        .clazz("retrace.InlineFunction")
+        .uniqueMethodWithName("inlineExceptionInstance")
+        .asFoundMethodSubject();
+  }
+
   @Test
   public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
     testForRuntime(parameters)
@@ -78,9 +93,10 @@
   public void testRetraceKotlinInlineStaticFunction()
       throws ExecutionException, CompilationFailedException, IOException {
     String main = "retrace.MainKt";
+    Path kotlinSources = compilationResults.apply(parameters.getRuntime());
+    CodeInspector kotlinInspector = new CodeInspector(kotlinSources);
     testForR8(parameters.getBackend())
-        .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
-        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .addProgramFiles(kotlinSources, ToolHelper.getKotlinStdlibJar())
         .addKeepAttributes("SourceFile", "LineNumberTable")
         .allowDiagnosticWarningMessages()
         .setMode(CompilationMode.RELEASE)
@@ -95,8 +111,7 @@
               MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
               InlinePosition inlineStack =
                   InlinePosition.stack(
-                      InlinePosition.create(
-                          "retrace.InlineFunctionKt", "inlineExceptionStatic", 2, 8),
+                      InlinePosition.create(inlineExceptionStatic(kotlinInspector), 2, 8),
                       InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 15));
               checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
             });
@@ -106,9 +121,10 @@
   public void testRetraceKotlinInlineInstanceFunction()
       throws ExecutionException, CompilationFailedException, IOException {
     String main = "retrace.MainInstanceKt";
+    Path kotlinSources = compilationResults.apply(parameters.getRuntime());
+    CodeInspector kotlinInspector = new CodeInspector(kotlinSources);
     testForR8(parameters.getBackend())
-        .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
-        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .addProgramFiles(kotlinSources, ToolHelper.getKotlinStdlibJar())
         .addKeepAttributes("SourceFile", "LineNumberTable")
         .allowDiagnosticWarningMessages()
         .setMode(CompilationMode.RELEASE)
@@ -123,8 +139,7 @@
               MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
               InlinePosition inlineStack =
                   InlinePosition.stack(
-                      InlinePosition.create(
-                          "retrace.InlineFunction", "inlineExceptionInstance", 2, 15),
+                      InlinePosition.create(inlineExceptionInstance(kotlinInspector), 2, 15),
                       InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 13));
               checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
             });
@@ -134,9 +149,10 @@
   public void testRetraceKotlinNestedInlineFunction()
       throws ExecutionException, CompilationFailedException, IOException {
     String main = "retrace.MainNestedKt";
+    Path kotlinSources = compilationResults.apply(parameters.getRuntime());
+    CodeInspector kotlinInspector = new CodeInspector(kotlinSources);
     testForR8(parameters.getBackend())
-        .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
-        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .addProgramFiles(kotlinSources, ToolHelper.getKotlinStdlibJar())
         .addKeepAttributes("SourceFile", "LineNumberTable")
         .allowDiagnosticWarningMessages()
         .setMode(CompilationMode.RELEASE)
@@ -151,8 +167,7 @@
               MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
               InlinePosition inlineStack =
                   InlinePosition.stack(
-                      InlinePosition.create(
-                          "retrace.InlineFunctionKt", "inlineExceptionStatic", 3, 8),
+                      InlinePosition.create(inlineExceptionStatic(kotlinInspector), 3, 8),
                       // TODO(b/146399675): There should be a nested frame on
                       //  retrace.NestedInlineFunctionKt.nestedInline(line 10).
                       InlinePosition.create(mainSubject.asFoundMethodSubject(), 3, 19));
@@ -164,9 +179,10 @@
   public void testRetraceKotlinNestedInlineFunctionOnFirstLine()
       throws ExecutionException, CompilationFailedException, IOException {
     String main = "retrace.MainNestedFirstLineKt";
+    Path kotlinSources = compilationResults.apply(parameters.getRuntime());
+    CodeInspector kotlinInspector = new CodeInspector(kotlinSources);
     testForR8(parameters.getBackend())
-        .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
-        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .addProgramFiles(kotlinSources, ToolHelper.getKotlinStdlibJar())
         .addKeepAttributes("SourceFile", "LineNumberTable")
         .allowDiagnosticWarningMessages()
         .setMode(CompilationMode.RELEASE)
@@ -181,8 +197,7 @@
               MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
               InlinePosition inlineStack =
                   InlinePosition.stack(
-                      InlinePosition.create(
-                          "retrace.InlineFunctionKt", "inlineExceptionStatic", 2, 8),
+                      InlinePosition.create(inlineExceptionStatic(kotlinInspector), 2, 8),
                       // TODO(b/146399675): There should be a nested frame on
                       //  retrace.NestedInlineFunctionKt.nestedInlineOnFirstLine(line 15).
                       InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 20));
@@ -201,7 +216,7 @@
             .streamInstructions()
             .filter(InstructionSubject::isThrow)
             .collect(toSingle())
-            .retracePosition(codeInspector.retrace());
+            .retraceLinePosition(codeInspector.retrace());
     assertThat(retraceResult, isInlineFrame());
     assertThat(retraceResult, isInlineStack(inlineStack));
     assertThat(stackTrace, containsInlinePosition(inlineStack));
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index cfc4fba..174dd5e 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.retrace.stacktraces.InlineNoLineNumberStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineWithLineNumbersStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InvalidStackTrace;
+import com.android.tools.r8.retrace.stacktraces.NamedModuleStackTrace;
 import com.android.tools.r8.retrace.stacktraces.NullStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ObfucatedExceptionClassStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ObfuscatedRangeToSingleLineStackTrace;
@@ -165,6 +166,12 @@
     runRetraceTest(new ObfuscatedRangeToSingleLineStackTrace());
   }
 
+  @Test
+  public void testBootLoaderAndNamedModulesStackTrace() {
+    assumeFalse(useRegExpParsing);
+    runRetraceTest(new NamedModuleStackTrace());
+  }
+
   private TestDiagnosticMessagesImpl runRetraceTest(StackTraceForTest stackTraceForTest) {
     TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
     RetraceCommand retraceCommand =
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/NamedModuleStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/NamedModuleStackTrace.java
new file mode 100644
index 0000000..8bd402f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/NamedModuleStackTrace.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2020, 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This is testing the string representation of stack trace elements with built-in class loaders and
+ * named/unnamed modules:
+ * https://docs.oracle.com/javase/10/docs/api/java/lang/StackTraceElement.html#toString()
+ */
+public class NamedModuleStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "SomeFakeException: this is a fake exception",
+        "\tat classloader.a.b.a/named_module@9.0/a.a(:101)",
+        "\tat classloader.a.b.a//a.b(App.java:12)",
+        "\tat named_module@2.1/a.c(Lib.java:80)",
+        "\tat named_module/a.d(Lib.java:81)",
+        "\tat a.e(MyClass.java:9)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.joinLines(
+        "com.android.tools.r8.Classloader -> classloader.a.b.a:",
+        "com.android.tools.r8.Main -> a:",
+        "  101:101:void main(java.lang.String[]):1:1 -> a",
+        "  12:12:void foo(java.lang.String[]):2:2 -> b",
+        "  80:80:void bar(java.lang.String[]):3:3 -> c",
+        "  81:81:void baz(java.lang.String[]):4:4 -> d",
+        "  9:9:void qux(java.lang.String[]):5:5 -> e");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "SomeFakeException: this is a fake exception",
+        "\tat com.android.tools.r8.Classloader/named_module@9.0/com.android.tools.r8.Main.main(Main.java:1)",
+        "\tat com.android.tools.r8.Classloader//com.android.tools.r8.Main.foo(Main.java:2)",
+        "\tat named_module@2.1/com.android.tools.r8.Main.bar(Main.java:3)",
+        "\tat named_module/com.android.tools.r8.Main.baz(Main.java:4)",
+        "\tat com.android.tools.r8.Main.qux(Main.java:5)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java
new file mode 100644
index 0000000..b201ce6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java
@@ -0,0 +1,481 @@
+// Copyright (c) 2020, 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.rewrite.assertions;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.AssertionsConfiguration;
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class AssertionConfigurationKotlinTest extends KotlinTestBase implements Opcodes {
+
+  private static final Package pkg = AssertionConfigurationKotlinTest.class.getPackage();
+  private static final String kotlintestclasesPackage = pkg.getName() + ".kotlintestclasses";
+  private static final String testClassKt = kotlintestclasesPackage + ".TestClassKt";
+  private static final String class1 = kotlintestclasesPackage + ".Class1";
+  private static final String class2 = kotlintestclasesPackage + ".Class2";
+
+  private static final Map<KotlinTargetVersion, Path> kotlinClasses = new HashMap<>();
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0},{1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), KotlinTargetVersion.values());
+  }
+
+  public AssertionConfigurationKotlinTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  @BeforeClass
+  public static void compileKotlin() throws Exception {
+    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+      Path ktClasses =
+          kotlinc(KOTLINC, targetVersion)
+              .addSourceFiles(getKotlinFilesInTestPackage(pkg))
+              .compile();
+      kotlinClasses.put(targetVersion, ktClasses);
+    }
+  }
+
+  private void runD8Test(
+      ThrowableConsumer<D8TestBuilder> builderConsumer,
+      ThrowingConsumer<CodeInspector, RuntimeException> inspector,
+      List<String> outputLines)
+      throws Exception {
+    testForD8()
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .addProgramFiles(kotlinClasses.get(targetVersion))
+        .setMinApi(parameters.getApiLevel())
+        .apply(builderConsumer)
+        .run(
+            parameters.getRuntime(),
+            getClass().getPackage().getName() + ".kotlintestclasses.TestClassKt")
+        .inspect(inspector)
+        .assertSuccessWithOutputLines(outputLines);
+  }
+
+  public void runR8Test(
+      ThrowableConsumer<R8FullTestBuilder> builderConsumer,
+      ThrowingConsumer<CodeInspector, RuntimeException> inspector,
+      List<String> outputLines)
+      throws Exception {
+    runR8Test(builderConsumer, inspector, outputLines, false);
+  }
+
+  public void runR8Test(
+      ThrowableConsumer<R8FullTestBuilder> builderConsumer,
+      ThrowingConsumer<CodeInspector, RuntimeException> inspector,
+      List<String> outputLines,
+      boolean enableJvmAssertions)
+      throws Exception {
+
+    testForR8(parameters.getBackend())
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .addProgramFiles(kotlinClasses.get(targetVersion))
+        .addKeepMainRule(testClassKt)
+        .addKeepClassAndMembersRules(class1, class2)
+        .setMinApi(parameters.getApiLevel())
+        .apply(builderConsumer)
+        .noMinification()
+        .allowDiagnosticWarningMessages()
+        .compile()
+        .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+        .enableRuntimeAssertions(enableJvmAssertions)
+        .run(parameters.getRuntime(), testClassKt)
+        .inspect(inspector)
+        .assertSuccessWithOutputLines(outputLines);
+  }
+
+  private List<String> allAssertionsExpectedLines() {
+    return ImmutableList.of("AssertionError in Class1", "AssertionError in Class2", "DONE");
+  }
+
+  private List<String> noAllAssertionsExpectedLines() {
+    return ImmutableList.of("DONE");
+  }
+
+  private void checkAssertionCodeRemoved(ClassSubject subject, boolean isR8) {
+    assertThat(subject, isPresent());
+    if (subject.getOriginalName().equals("kotlin._Assertions")) {
+      // With R8 the static-put of the kotlin._Assertions.INSTANCE field is removed as well,
+      // as is not used.
+      assertEquals(
+          (isR8 ? 0 : 1),
+          subject
+              .uniqueMethodWithName("<clinit>")
+              .streamInstructions()
+              .filter(InstructionSubject::isStaticPut)
+              .count());
+      assertFalse(
+          subject
+              .uniqueMethodWithName("<clinit>")
+              .streamInstructions()
+              .anyMatch(InstructionSubject::isConstNumber));
+    } else {
+      // In R8 the false (default) value of kotlin._Assertions.ENABLED is propagated.
+      assertEquals(
+          !isR8,
+          subject
+              .uniqueMethodWithName("m")
+              .streamInstructions()
+              .anyMatch(InstructionSubject::isThrow));
+    }
+  }
+
+  private void checkAssertionCodeRemoved(CodeInspector inspector, String clazz, boolean isR8) {
+    checkAssertionCodeRemoved(inspector.clazz(clazz), isR8);
+  }
+
+  private void checkAssertionCodeEnabled(ClassSubject subject, boolean isR8) {
+    assertThat(subject, isPresent());
+    if (subject.getOriginalName().equals("kotlin._Assertions")) {
+      // With R8 the static-put of the kotlin._Assertions.INSTANCE field is removed as is not used.
+      assertEquals(
+          (isR8 ? 1 : 2),
+          subject
+              .uniqueMethodWithName("<clinit>")
+              .streamInstructions()
+              .filter(InstructionSubject::isStaticPut)
+              .count());
+      assertTrue(
+          subject
+              .uniqueMethodWithName("<clinit>")
+              .streamInstructions()
+              .anyMatch(instruction -> instruction.isConstNumber(1)));
+    } else {
+      assertTrue(
+          subject
+              .uniqueMethodWithName("m")
+              .streamInstructions()
+              .anyMatch(InstructionSubject::isThrow));
+    }
+  }
+
+  private void checkAssertionCodeEnabled(CodeInspector inspector, String clazz, boolean isR8) {
+
+    checkAssertionCodeEnabled(inspector.clazz(clazz), isR8);
+  }
+
+  private void checkAssertionCodeLeft(CodeInspector inspector, String clazz, boolean isR8) {
+    ClassSubject subject = inspector.clazz(clazz);
+    assertThat(subject, isPresent());
+    if (subject.getOriginalName().equals("kotlin._Assertions")) {
+      // With R8 the static-put of the kotlin._Assertions.INSTANCE field is removed as is not used.
+      assertEquals(
+          (isR8 ? 1 : 2),
+          subject
+              .uniqueMethodWithName("<clinit>")
+              .streamInstructions()
+              .filter(InstructionSubject::isStaticPut)
+              .count());
+      assertFalse(
+          subject
+              .uniqueMethodWithName("<clinit>")
+              .streamInstructions()
+              .anyMatch(InstructionSubject::isConstNumber));
+    } else {
+      assertTrue(
+          subject
+              .uniqueMethodWithName("m")
+              .streamInstructions()
+              .anyMatch(InstructionSubject::isThrow));
+    }
+  }
+
+  private void checkAssertionCodeRemoved(CodeInspector inspector, boolean isR8) {
+    if (isR8) {
+      assertThat(inspector.clazz("kotlin._Assertions"), not(isPresent()));
+    } else {
+      checkAssertionCodeRemoved(inspector, "kotlin._Assertions", isR8);
+    }
+    checkAssertionCodeRemoved(inspector, class1, isR8);
+    checkAssertionCodeRemoved(inspector, class2, isR8);
+  }
+
+  private void checkAssertionCodeEnabled(CodeInspector inspector, boolean isR8) {
+    if (isR8) {
+      assertThat(inspector.clazz("kotlin._Assertions"), not(isPresent()));
+    } else {
+      checkAssertionCodeEnabled(inspector, "kotlin._Assertions", isR8);
+    }
+    checkAssertionCodeEnabled(inspector, class1, isR8);
+    checkAssertionCodeEnabled(inspector, class2, isR8);
+  }
+
+  private void checkAssertionCodeLeft(CodeInspector inspector, boolean isR8) {
+    checkAssertionCodeLeft(inspector, "kotlin._Assertions", isR8);
+    checkAssertionCodeLeft(inspector, class1, isR8);
+    checkAssertionCodeLeft(inspector, class2, isR8);
+  }
+
+  @Test
+  public void testAssertionsForDex() throws Exception {
+    Assume.assumeTrue(parameters.isDexRuntime());
+    // Leaving assertions in or disabling them on Dalvik/Art means no assertions.
+    runD8Test(
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::passthroughAllAssertions),
+        inspector -> checkAssertionCodeLeft(inspector, false),
+        noAllAssertionsExpectedLines());
+    runR8Test(
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::passthroughAllAssertions),
+        inspector -> checkAssertionCodeLeft(inspector, true),
+        noAllAssertionsExpectedLines());
+    runD8Test(
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::disableAllAssertions),
+        inspector -> checkAssertionCodeRemoved(inspector, false),
+        noAllAssertionsExpectedLines());
+    runR8Test(
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::disableAllAssertions),
+        inspector -> checkAssertionCodeRemoved(inspector, true),
+        noAllAssertionsExpectedLines());
+    // Compile time enabling assertions gives assertions on Dalvik/Art.
+    runD8Test(
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::enableAllAssertions),
+        inspector -> checkAssertionCodeEnabled(inspector, false),
+        allAssertionsExpectedLines());
+    runR8Test(
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::enableAllAssertions),
+        inspector -> checkAssertionCodeEnabled(inspector, true),
+        allAssertionsExpectedLines());
+    // Enabling for the "kotlin._Assertions" class should enable all.
+    runD8Test(
+        builder ->
+            builder.addAssertionsConfiguration(
+                b -> b.setEnable().setScopeClass("kotlin._Assertions").build()),
+        inspector -> checkAssertionCodeEnabled(inspector, false),
+        allAssertionsExpectedLines());
+    runR8Test(
+        builder ->
+            builder.addAssertionsConfiguration(
+                b -> b.setEnable().setScopeClass("kotlin._Assertions").build()),
+        inspector -> checkAssertionCodeEnabled(inspector, true),
+        allAssertionsExpectedLines());
+    // Enabling for the "kotlin" package should enable all.
+    runD8Test(
+        builder ->
+            builder.addAssertionsConfiguration(
+                b -> b.setEnable().setScopePackage("kotlin").build()),
+        inspector -> checkAssertionCodeEnabled(inspector, false),
+        allAssertionsExpectedLines());
+    runR8Test(
+        builder ->
+            builder.addAssertionsConfiguration(
+                b -> b.setEnable().setScopePackage("kotlin").build()),
+        inspector -> checkAssertionCodeEnabled(inspector, true),
+        allAssertionsExpectedLines());
+  }
+
+  @Test
+  public void testAssertionsForCf() throws Exception {
+    Assume.assumeTrue(parameters.isCfRuntime());
+    // Leaving assertion code means assertions are controlled by the -ea flag.
+    runR8Test(
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::passthroughAllAssertions),
+        inspector -> checkAssertionCodeLeft(inspector, true),
+        noAllAssertionsExpectedLines());
+    runR8Test(
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::passthroughAllAssertions),
+        inspector -> checkAssertionCodeLeft(inspector, true),
+        allAssertionsExpectedLines(),
+        true);
+    // Compile time enabling or disabling assertions means the -ea flag has no effect.
+    runR8Test(
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::enableAllAssertions),
+        inspector -> checkAssertionCodeEnabled(inspector, true),
+        allAssertionsExpectedLines());
+    runR8Test(
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::enableAllAssertions),
+        inspector -> checkAssertionCodeEnabled(inspector, true),
+        allAssertionsExpectedLines(),
+        true);
+    runR8Test(
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::disableAllAssertions),
+        inspector -> checkAssertionCodeRemoved(inspector, true),
+        noAllAssertionsExpectedLines());
+    runR8Test(
+        builder ->
+            builder.addAssertionsConfiguration(
+                AssertionsConfiguration.Builder::disableAllAssertions),
+        inspector -> checkAssertionCodeRemoved(inspector, true),
+        noAllAssertionsExpectedLines(),
+        true);
+  }
+
+  @Test
+  public void TestWithModifiedKotlinAssertions() throws Exception {
+    Assume.assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addProgramClassFileData(dumpModifiedKotlinAssertions())
+        .addProgramFiles(kotlinClasses.get(targetVersion))
+        .setMinApi(parameters.getApiLevel())
+        .addAssertionsConfiguration(AssertionsConfiguration.Builder::passthroughAllAssertions)
+        .run(
+            parameters.getRuntime(),
+            getClass().getPackage().getName() + ".kotlintestclasses.TestClassKt")
+        .assertSuccessWithOutputLines(noAllAssertionsExpectedLines());
+    testForD8()
+        .addProgramClassFileData(dumpModifiedKotlinAssertions())
+        .addProgramFiles(kotlinClasses.get(targetVersion))
+        .setMinApi(parameters.getApiLevel())
+        .addAssertionsConfiguration(AssertionsConfiguration.Builder::enableAllAssertions)
+        .run(
+            parameters.getRuntime(),
+            getClass().getPackage().getName() + ".kotlintestclasses.TestClassKt")
+        .assertSuccessWithOutputLines(allAssertionsExpectedLines());
+    testForD8()
+        .addProgramClassFileData(dumpModifiedKotlinAssertions())
+        .addProgramFiles(kotlinClasses.get(targetVersion))
+        .setMinApi(parameters.getApiLevel())
+        .addAssertionsConfiguration(AssertionsConfiguration.Builder::disableAllAssertions)
+        .run(
+            parameters.getRuntime(),
+            getClass().getPackage().getName() + ".kotlintestclasses.TestClassKt")
+        .assertSuccessWithOutputLines(noAllAssertionsExpectedLines());
+  }
+
+  // Slightly modified version of kotlin._Assertions to hit all code paths in the assertion
+  // rewriter. See "Code added" below.
+  public static byte[] dumpModifiedKotlinAssertions() {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    FieldVisitor fieldVisitor;
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(
+        V1_6,
+        ACC_PUBLIC | ACC_FINAL | ACC_SUPER,
+        "kotlin/_Assertions",
+        null,
+        "java/lang/Object",
+        null);
+
+    {
+      fieldVisitor =
+          classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "ENABLED", "Z", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      fieldVisitor =
+          classWriter.visitField(
+              ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "INSTANCE", "Lkotlin/_Assertions;", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_STATIC | ACC_SYNTHETIC | ACC_DEPRECATED,
+              "ENABLED$annotations",
+              "()V",
+              null,
+              null);
+      methodVisitor.visitCode();
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(0, 0);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+      methodVisitor.visitCode();
+      methodVisitor.visitTypeInsn(NEW, "kotlin/_Assertions");
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "kotlin/_Assertions", "<init>", "()V", false);
+      methodVisitor.visitVarInsn(ASTORE, 0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitFieldInsn(
+          PUTSTATIC, "kotlin/_Assertions", "INSTANCE", "Lkotlin/_Assertions;");
+
+      // Code added (added an additional call to getClass().desiredAssertionStatus() which
+      // result is not assigned to ENABLED).
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Class", "desiredAssertionStatus", "()Z", false);
+      methodVisitor.visitInsn(POP);
+      // End code added.
+
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Class", "desiredAssertionStatus", "()Z", false);
+      methodVisitor.visitFieldInsn(PUTSTATIC, "kotlin/_Assertions", "ENABLED", "Z");
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 1);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
index 2d79d48..01c28c1 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
@@ -481,16 +481,33 @@
             "AssertionError in TestClassForInnerClass.InnerClass",
             "DONE");
   }
+
   /**
    * Code for the following class in the unnamed package:
    *
-   * <p>public class Main { public static void main(String[] args) { try { Class1.m(); } catch
-   * (AssertionError e) { System.out.println("AssertionError in Class1"); } try { Class2.m(); }
-   * catch (AssertionError e) { System.out.println("AssertionError in Class2"); } try {
-   * com.android.tools.r8.rewrite.assertions.Class1.m(); } catch (AssertionError e) {
-   * System.out.println("AssertionError in Class1"); } try {
-   * com.android.tools.r8.rewrite.assertions.Class2.m(); } catch (AssertionError e) {
-   * System.out.println("AssertionError in Class2"); } System.out.println("DONE"); } }
+   * <pre>
+   *   public class Main {
+   *     public static void main(String[] args) {
+   *       try {
+   *         Class1.m();
+   *       } catch (AssertionError e) {
+   *         System.out.println("AssertionError in Class1");
+   *       } try {
+   *         Class2.m();
+   *       } catch (AssertionError e) {
+   *         System.out.println("AssertionError in Class2");
+   *       } try {
+   *         com.android.tools.r8.rewrite.assertions.Class1.m();
+   *       } catch (AssertionError e) {
+   *         System.out.println("AssertionError in Class1");
+   *       } try {
+   *         com.android.tools.r8.rewrite.assertions.Class2.m();
+   *       } catch (AssertionError e) {
+   *         System.out.println("AssertionError in Class2");
+   *       } System.out.println("DONE");
+   *     }
+   *   }
+   * </pre>
    */
   public static byte[] testClassForUnknownPackage() {
 
@@ -642,7 +659,13 @@
   /**
    * Code for the following class in the unnamed package:
    *
-   * <p>public class <name> { public static void m() { assert false; } }
+   * <pre>
+   *   public class <name> {
+   *     public static void m() {
+   *       assert false;
+   *     }
+   *   }
+   * </pre>
    */
   public static byte[] classInUnnamedPackage(String name) {
 
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/kotlintestclasses/Class1.kt b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlintestclasses/Class1.kt
new file mode 100644
index 0000000..582c029
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlintestclasses/Class1.kt
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, 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.rewrite.assertions.kotlintestclasses
+
+class Class1 {
+  fun m() {
+    assert(hashCode() == 0)
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/kotlintestclasses/Class2.kt b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlintestclasses/Class2.kt
new file mode 100644
index 0000000..2936e6e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlintestclasses/Class2.kt
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, 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.rewrite.assertions.kotlintestclasses
+
+class Class2 {
+  fun m() {
+    assert(false)
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/kotlintestclasses/TestClass.kt b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlintestclasses/TestClass.kt
new file mode 100644
index 0000000..990999c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlintestclasses/TestClass.kt
@@ -0,0 +1,19 @@
+// Copyright (c) 2020, 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.rewrite.assertions.kotlintestclasses
+
+fun main() {
+  try {
+    Class1().m()
+  } catch (e: AssertionError) {
+    println("AssertionError in Class1")
+  }
+  try {
+    Class2().m()
+  } catch (e: AssertionError) {
+    println("AssertionError in Class2")
+  }
+  println("DONE")
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java b/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
index c1998bd..e64933b 100644
--- a/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
@@ -9,9 +9,9 @@
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
@@ -21,7 +21,7 @@
 public class R8Shaking2LookupTest {
 
   static final String APP_FILE_NAME = ToolHelper.EXAMPLES_BUILD_DIR + "shaking2/classes.dex";
-  private DexApplication program;
+  private DirectMappedDexApplication program;
   private DexItemFactory dexItemFactory;
   private AppInfoWithSubtyping appInfo;
 
diff --git a/src/test/java/com/android/tools/r8/shaking/b113138046/NativeMethodTest.java b/src/test/java/com/android/tools/r8/shaking/b113138046/NativeMethodTest.java
index f1d0cda..15fba7f 100644
--- a/src/test/java/com/android/tools/r8/shaking/b113138046/NativeMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/b113138046/NativeMethodTest.java
@@ -85,7 +85,6 @@
         "-keep class " + Outer.class.getCanonicalName() + " {",
         "  onEvent(...);",
         "}",
-        "-printmapping",
         "-keepattributes InnerClasses,EnclosingMethod,Signature",
         "-allowaccessmodification");
     test(config, compatMode);
@@ -106,7 +105,6 @@
         "  @**.Keep <fields>;",
         "  @**.Keep <methods>;",
         "}",
-        "-printmapping",
         "-keepattributes InnerClasses,EnclosingMethod,Signature",
         "-allowaccessmodification");
     test(config, true);
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
index ab46041..7752712 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
@@ -243,11 +243,11 @@
   public void testStaticFieldWithoutInitializationStaticClassKept() throws Exception {
     // An explicit keep rule keeps the default constructor.
     Class<?> mainClass = MainGetStaticFieldNotInitialized.class;
-    String proguardConfiguration = keepMainProguardConfiguration(
-        mainClass,
-        ImmutableList.of(
-            "-keep class " + getJavacGeneratedClassName(StaticFieldNotInitialized.class) + " {",
-            "}"));
+    String proguardConfiguration =
+        keepMainProguardConfiguration(
+            mainClass,
+            ImmutableList.of(
+                "-keep class " + StaticFieldNotInitialized.class.getTypeName() + " {", "}"));
     runTest(
         mainClass,
         ImmutableList.of(mainClass, StaticFieldNotInitialized.class),
@@ -259,11 +259,11 @@
   public void testStaticFieldWithInitializationStaticClassKept() throws Exception {
     // An explicit keep rule keeps the default constructor.
     Class<?> mainClass = MainGetStaticFieldInitialized.class;
-    String proguardConfiguration = keepMainProguardConfiguration(
-        mainClass,
-        ImmutableList.of(
-            "-keep class " + getJavacGeneratedClassName(StaticFieldInitialized.class) + " {",
-            "}"));
+    String proguardConfiguration =
+        keepMainProguardConfiguration(
+            mainClass,
+            ImmutableList.of(
+                "-keep class " + StaticFieldInitialized.class.getTypeName() + " {", "}"));
     runTest(
         mainClass,
         ImmutableList.of(mainClass, StaticFieldInitialized.class),
@@ -275,11 +275,10 @@
   public void testStaticMethodStaticClassKept() throws Exception {
     // An explicit keep rule keeps the default constructor.
     Class<?> mainClass = MainCallStaticMethod.class;
-    String proguardConfiguration = keepMainProguardConfiguration(
-        mainClass,
-        ImmutableList.of(
-            "-keep class " + getJavacGeneratedClassName(StaticMethod.class) + " {",
-            "}"));
+    String proguardConfiguration =
+        keepMainProguardConfiguration(
+            mainClass,
+            ImmutableList.of("-keep class " + StaticMethod.class.getTypeName() + " {", "}"));
     runTest(
         mainClass,
         ImmutableList.of(mainClass, StaticMethod.class),
diff --git a/src/test/java/com/android/tools/r8/testing/UnicodeUtf8Test.java b/src/test/java/com/android/tools/r8/testing/UnicodeUtf8Test.java
new file mode 100644
index 0000000..85264d1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/testing/UnicodeUtf8Test.java
@@ -0,0 +1,51 @@
+// Copyright (c) 2020, 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.testing;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/* This test is ensuring that javac will compile expecting an UTF-8 encoding of the test. */
+@RunWith(Parameterized.class)
+public class UnicodeUtf8Test extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public UnicodeUtf8Test(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("\uD83C\uDCA1");
+  }
+
+  public static class Main {
+
+    private static final String MOTOR_HEAD = "🂡";
+
+    public static void main(String[] args) throws UnsupportedEncodingException {
+      (new PrintStream(System.out, true, "UTF-8")).println(MOTOR_HEAD);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/ThrowingOutputStream.java b/src/test/java/com/android/tools/r8/utils/ThrowingOutputStream.java
new file mode 100644
index 0000000..2542264
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/ThrowingOutputStream.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2020, 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.io.OutputStream;
+import java.util.function.Supplier;
+
+public class ThrowingOutputStream<T extends Error> extends OutputStream {
+
+  private final Supplier<T> exceptionSupplier;
+
+  public ThrowingOutputStream(Supplier<T> exceptionSupplier) {
+    this.exceptionSupplier = exceptionSupplier;
+  }
+
+  @Override
+  public void write(int b) {
+    throw exceptionSupplier.get();
+  }
+
+  @Override
+  public void write(byte[] b) {
+    throw exceptionSupplier.get();
+  }
+
+  @Override
+  public void write(byte[] b, int off, int len) {
+    throw exceptionSupplier.get();
+  }
+
+  @Override
+  public void flush() {
+    throw exceptionSupplier.get();
+  }
+
+  @Override
+  public void close() {
+    throw exceptionSupplier.get();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
index 1a640ed..c467da4 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
@@ -9,6 +9,11 @@
 public class AbsentKmClassSubject extends KmClassSubject {
 
   @Override
+  public String getName() {
+    return null;
+  }
+
+  @Override
   public DexClass getDexClass() {
     return null;
   }
@@ -99,6 +104,16 @@
   }
 
   @Override
+  public List<String> getNestedClassDescriptors() {
+    return null;
+  }
+
+  @Override
+  public List<ClassSubject> getNestedClasses() {
+    return null;
+  }
+
+  @Override
   public List<String> getSealedSubclassDescriptors() {
     return null;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java
index e869207..c72d305 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java
@@ -31,4 +31,14 @@
   public JvmMethodSignature signature() {
     return null;
   }
+
+  @Override
+  public KmTypeSubject receiverParameterType() {
+    return null;
+  }
+
+  @Override
+  public KmTypeSubject returnType() {
+    return null;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
index 6c65c2d..7b57397 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
@@ -24,6 +24,11 @@
   }
 
   @Override
+  public String getName() {
+    return kmClass.getName();
+  }
+
+  @Override
   public DexClass getDexClass() {
     return clazz;
   }
@@ -58,7 +63,7 @@
   @Override
   public List<String> getSuperTypeDescriptors() {
     return kmClass.getSupertypes().stream()
-        .map(this::getDescriptorFromKmType)
+        .map(KmTypeSubject::getDescriptorFromKmType)
         .filter(Objects::nonNull)
         .collect(Collectors.toList());
   }
@@ -71,6 +76,26 @@
         .collect(Collectors.toList());
   }
 
+  private String nestClassDescriptor(String nestClassName) {
+    return DescriptorUtils.getDescriptorFromClassBinaryName(
+        kmClass.name + DescriptorUtils.INNER_CLASS_SEPARATOR + nestClassName);
+  }
+
+  @Override
+  public List<String> getNestedClassDescriptors() {
+    return kmClass.getNestedClasses().stream()
+        .map(this::nestClassDescriptor)
+        .collect(Collectors.toList());
+  }
+
+  @Override
+  public List<ClassSubject> getNestedClasses() {
+    return kmClass.getNestedClasses().stream()
+        .map(this::nestClassDescriptor)
+        .map(this::getClassSubjectFromDescriptor)
+        .collect(Collectors.toList());
+  }
+
   @Override
   public List<String> getSealedSubclassDescriptors() {
     return kmClass.getSealedSubclasses().stream()
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
index 9fece50..b9fa99a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
@@ -3,10 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils.codeinspector;
 
-import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKotlinClassifier;
+import static com.android.tools.r8.utils.codeinspector.KmTypeSubject.getDescriptorFromKmType;
 
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.utils.Box;
 import java.util.List;
 import java.util.Objects;
 import java.util.stream.Collectors;
@@ -19,7 +18,6 @@
 import kotlinx.metadata.KmPropertyExtensionVisitor;
 import kotlinx.metadata.KmPropertyVisitor;
 import kotlinx.metadata.KmType;
-import kotlinx.metadata.KmTypeVisitor;
 import kotlinx.metadata.jvm.JvmFieldSignature;
 import kotlinx.metadata.jvm.JvmFunctionExtensionVisitor;
 import kotlinx.metadata.jvm.JvmMethodSignature;
@@ -30,26 +28,6 @@
   CodeInspector codeInspector();
   KmDeclarationContainer getKmDeclarationContainer();
 
-  // TODO(b/145824437): This is a dup of DescriptorUtils#getDescriptorFromKmType
-  default String getDescriptorFromKmType(KmType kmType) {
-    if (kmType == null) {
-      return null;
-    }
-    Box<String> descriptor = new Box<>(null);
-    kmType.accept(new KmTypeVisitor() {
-      @Override
-      public void visitClass(String name) {
-        descriptor.set(getDescriptorFromKotlinClassifier(name));
-      }
-
-      @Override
-      public void visitTypeAlias(String name) {
-        descriptor.set(getDescriptorFromKotlinClassifier(name));
-      }
-    });
-    return descriptor.get();
-  }
-
   @Override
   default List<String> getParameterTypeDescriptorsInFunctions() {
     return getKmDeclarationContainer().getFunctions().stream()
@@ -97,6 +75,8 @@
           };
         }
       });
+      // We don't check Kotlin types in tests, but be aware of the relocation issue.
+      // See b/70169921#comment57 for more details.
     }
   }
 
@@ -202,6 +182,8 @@
           };
         }
       });
+      // We don't check Kotlin types in tests, but be aware of the relocation issue.
+      // See b/70169921#comment57 for more details.
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
index 7c970c2..3bbf235 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.utils.codeinspector.FoundKmDeclarationContainerSubject.KmFunctionProcessor;
 import kotlinx.metadata.KmFunction;
+import kotlinx.metadata.KmType;
 import kotlinx.metadata.jvm.JvmMethodSignature;
 
 public class FoundKmFunctionSubject extends KmFunctionSubject {
@@ -46,4 +47,16 @@
   public JvmMethodSignature signature() {
     return signature;
   }
+
+  @Override
+  public KmTypeSubject receiverParameterType() {
+    KmType kmType = kmFunction.getReceiverParameterType();
+    assert !isExtension() || kmType != null;
+    return kmType == null ? null : new KmTypeSubject(kmType);
+  }
+
+  @Override
+  public KmTypeSubject returnType() {
+    return new KmTypeSubject(kmFunction.getReturnType());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index ebe2f02..9a48dfa 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -115,15 +115,22 @@
   MethodSubject getMethodSubject();
 
   default int getLineNumber() {
-    return getMethodSubject().getLineNumberTable().getLineForInstruction(this);
+    LineNumberTable lineNumberTable = getMethodSubject().getLineNumberTable();
+    return lineNumberTable == null ? -1 : lineNumberTable.getLineForInstruction(this);
   }
 
-  default RetraceMethodResult retracePosition(RetraceBase retraceBase) {
+  default RetraceMethodResult retrace(RetraceBase retraceBase) {
     MethodSubject methodSubject = getMethodSubject();
     assert methodSubject.isPresent();
-    int lineNumber = getLineNumber();
-    return retraceBase
-        .retrace(methodSubject.asFoundMethodSubject().asMethodReference())
-        .narrowByLine(lineNumber);
+    return retraceBase.retrace(methodSubject.asFoundMethodSubject().asMethodReference());
+  }
+
+  default RetraceMethodResult retraceLinePosition(RetraceBase retraceBase) {
+    return retrace(retraceBase).narrowByLine(getLineNumber());
+  }
+
+  default RetraceMethodResult retracePcPosition(
+      RetraceBase retraceBase, MethodSubject methodSubject) {
+    return retrace(retraceBase).narrowByLine(getOffset(methodSubject).offset);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
index 97b233d..7fb1f9f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
@@ -7,12 +7,18 @@
 import java.util.List;
 
 public abstract class KmClassSubject extends Subject implements KmDeclarationContainerSubject {
+  public abstract String getName();
+
   public abstract DexClass getDexClass();
 
   public abstract List<String> getSuperTypeDescriptors();
 
   public abstract List<ClassSubject> getSuperTypes();
 
+  public abstract List<String> getNestedClassDescriptors();
+
+  public abstract List<ClassSubject> getNestedClasses();
+
   public abstract List<String> getSealedSubclassDescriptors();
 
   public abstract List<ClassSubject> getSealedSubclasses();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java
index 2e9eeda..d0e0a37 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java
@@ -15,4 +15,8 @@
   public abstract boolean isExtension();
 
   public abstract JvmMethodSignature signature();
+
+  public abstract KmTypeSubject receiverParameterType();
+
+  public abstract KmTypeSubject returnType();
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
new file mode 100644
index 0000000..aa52c67
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2020, 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.codeinspector;
+
+import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKotlinClassifier;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.Box;
+import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeVisitor;
+
+public class KmTypeSubject extends Subject {
+  private final KmType kmType;
+
+  KmTypeSubject(KmType kmType) {
+    assert kmType != null;
+    this.kmType = kmType;
+  }
+
+  // TODO(b/145824437): This is a dup of DescriptorUtils#getDescriptorFromKmType
+  static String getDescriptorFromKmType(KmType kmType) {
+    if (kmType == null) {
+      return null;
+    }
+    Box<String> descriptor = new Box<>(null);
+    kmType.accept(new KmTypeVisitor() {
+      @Override
+      public void visitClass(String name) {
+        // We don't check Kotlin types in tests, but be aware of the relocation issue.
+        // See b/70169921#comment25 for more details.
+        assert descriptor.get() == null;
+        descriptor.set(getDescriptorFromKotlinClassifier(name));
+      }
+
+      @Override
+      public void visitTypeAlias(String name) {
+        assert descriptor.get() == null;
+        descriptor.set(getDescriptorFromKotlinClassifier(name));
+      }
+    });
+    return descriptor.get();
+  }
+
+  public String descriptor() {
+    return getDescriptorFromKmType(kmType);
+  }
+
+  @Override
+  public boolean isPresent() {
+    return true;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    throw new Unreachable("Cannot determine if a type is renamed");
+  }
+
+  @Override
+  public boolean isSynthetic() {
+    throw new Unreachable("Cannot determine if a type is synthetic");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index d8d219e..3fb684f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.retrace.RetraceMethodResult;
 import com.android.tools.r8.retrace.RetraceMethodResult.Element;
 import com.android.tools.r8.utils.Box;
-import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -443,21 +442,7 @@
                 returnValue.set(false);
                 return;
               }
-              if (currentInline.hasMethodSubject()) {
-                sameMethod =
-                    element
-                        .getMethodReference()
-                        .equals(
-                            currentInline.methodSubject.asFoundMethodSubject().asMethodReference());
-              } else {
-                MethodReference methodReference = element.getMethodReference();
-                sameMethod =
-                    methodReference.getMethodName().equals(currentInline.methodName)
-                        || methodReference
-                            .getHolderClass()
-                            .getTypeName()
-                            .equals(currentInline.holder);
-              }
+              sameMethod = element.getMethodReference().equals(currentInline.methodReference);
               boolean samePosition =
                   element.getOriginalLineNumber(currentInline.minifiedPosition)
                       == currentInline.originalPosition;
@@ -476,29 +461,33 @@
     };
   }
 
-  public static Matcher<RetraceMethodResult> isInlinedInto(InlinePosition inlinePosition) {
+  public static Matcher<RetraceMethodResult> isTopOfStackTrace(
+      StackTrace stackTrace, List<Integer> minifiedPositions) {
     return new TypeSafeMatcher<RetraceMethodResult>() {
       @Override
       protected boolean matchesSafely(RetraceMethodResult item) {
-        if (item.isAmbiguous() || !inlinePosition.methodSubject.isPresent()) {
+        List<Element> retraceElements = item.stream().collect(Collectors.toList());
+        if (retraceElements.size() > stackTrace.size()
+            || retraceElements.size() != minifiedPositions.size()) {
           return false;
         }
-        List<Element> references = item.stream().collect(Collectors.toList());
-        if (references.size() < 2) {
-          return false;
+        for (int i = 0; i < retraceElements.size(); i++) {
+          Element retraceElement = retraceElements.get(i);
+          StackTraceLine stackTraceLine = stackTrace.get(i);
+          MethodReference methodReference = retraceElement.getMethodReference();
+          if (!stackTraceLine.methodName.equals(methodReference.getMethodName())
+              || !stackTraceLine.className.equals(methodReference.getHolderClass().getTypeName())
+              || stackTraceLine.lineNumber
+                  != retraceElement.getOriginalLineNumber(minifiedPositions.get(i))) {
+            return false;
+          }
         }
-        Element lastElement = ListUtils.last(references);
-        if (!lastElement
-            .getMethodReference()
-            .equals(inlinePosition.methodSubject.asFoundMethodSubject().asMethodReference())) {
-          return false;
-        }
-        return lastElement.getFirstLineNumberOfOriginalRange() == inlinePosition.originalPosition;
+        return true;
       }
 
       @Override
       public void describeTo(Description description) {
-        description.appendText("is not inlined into " + inlinePosition.getMethodName());
+        description.appendText("is not matching the stack trace");
       }
     };
   }
@@ -538,37 +527,27 @@
   }
 
   public static class InlinePosition {
-    private final FoundMethodSubject methodSubject;
-    private final String holder;
-    private final String methodName;
+    private final MethodReference methodReference;
     private final int minifiedPosition;
     private final int originalPosition;
 
     private InlinePosition caller;
 
     private InlinePosition(
-        FoundMethodSubject methodSubject,
-        String holder,
-        String methodName,
-        int minifiedPosition,
-        int originalPosition) {
-      this.methodSubject = methodSubject;
-      this.holder = holder;
-      this.methodName = methodName;
+        MethodReference methodReference, int minifiedPosition, int originalPosition) {
+      this.methodReference = methodReference;
       this.minifiedPosition = minifiedPosition;
       this.originalPosition = originalPosition;
-      assert methodSubject != null || holder != null;
-      assert methodSubject != null || methodName != null;
+    }
+
+    public static InlinePosition create(
+        MethodReference methodReference, int minifiedPosition, int originalPosition) {
+      return new InlinePosition(methodReference, minifiedPosition, originalPosition);
     }
 
     public static InlinePosition create(
         FoundMethodSubject methodSubject, int minifiedPosition, int originalPosition) {
-      return new InlinePosition(methodSubject, null, null, minifiedPosition, originalPosition);
-    }
-
-    public static InlinePosition create(
-        String holder, String methodName, int minifiedPosition, int originalPosition) {
-      return new InlinePosition(null, holder, methodName, minifiedPosition, originalPosition);
+      return create(methodSubject.asMethodReference(), minifiedPosition, originalPosition);
     }
 
     public static InlinePosition stack(InlinePosition... stack) {
@@ -585,18 +564,12 @@
       setCaller(index + 1, stack);
     }
 
-    boolean hasMethodSubject() {
-      return methodSubject != null;
-    }
-
     String getMethodName() {
-      return hasMethodSubject() ? methodSubject.getOriginalName(false) : methodName;
+      return methodReference.getMethodName();
     }
 
     String getClassName() {
-      return hasMethodSubject()
-          ? methodSubject.asMethodReference().getHolderClass().getTypeName()
-          : holder;
+      return methodReference.getHolderClass().getTypeName();
     }
   }
 }
diff --git a/src/test/sampleApks/simple/res/layout/main.xml b/src/test/sampleApks/simple/res/layout/main.xml
index 7859435..ab673af 100644
--- a/src/test/sampleApks/simple/res/layout/main.xml
+++ b/src/test/sampleApks/simple/res/layout/main.xml
@@ -14,5 +14,5 @@
       android:layout_height="wrap_content"
       android:layout_width="match_parent" android:id="@+id/PressButton"
       android:layout_margin="2dip"
-      android:text="Do something"/>
+      android:text="@string/referenced_from_layout"/>
 </LinearLayout>
diff --git a/src/test/sampleApks/simple/res/values/strings.xml b/src/test/sampleApks/simple/res/values/strings.xml
index 8bae26c..5bf4844 100644
--- a/src/test/sampleApks/simple/res/values/strings.xml
+++ b/src/test/sampleApks/simple/res/values/strings.xml
@@ -6,4 +6,7 @@
 -->
 <resources>
   <string name="app_name">R8 simple app</string>
+  <string name="referenced_from_layout">Push it</string>
+  <string name="referenced_from_code">code txt</string>
+  <string name="not_used">not used</string>
 </resources>
diff --git a/src/test/sampleApks/simple/src/com/android/tools/r8/sample/simple/R8Activity.java b/src/test/sampleApks/simple/src/com/android/tools/r8/sample/simple/R8Activity.java
index 4a09fd4..67aca2b 100644
--- a/src/test/sampleApks/simple/src/com/android/tools/r8/sample/simple/R8Activity.java
+++ b/src/test/sampleApks/simple/src/com/android/tools/r8/sample/simple/R8Activity.java
@@ -7,11 +7,40 @@
 import android.app.Activity;
 import android.os.Bundle;
 import com.android.tools.r8.sample.simple.R;
+import java.util.ArrayList;
+import android.content.res.Resources;
+
 
 public class R8Activity extends Activity {
   public void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setTheme(android.R.style.Theme_Light);
     setContentView(R.layout.main);
+    System.out.println(R.string.referenced_from_code);
+    useResources();
+  }
+
+
+  public void useResources() {
+    // Add usage of resource identifiers with ID's that would never
+    // exist in aapt generated R class (so that we can swap them out).
+    ArrayList<Integer> resources = new ArrayList();
+    resources.add(0x7fDEAD01);
+    resources.add(0x7fDEAD02);
+    resources.add(0x7fDEAD03);
+    resources.add(0x7fDEAD04);
+    resources.add(0x7fDEAD05);
+    resources.add(0x7fDEAD06);
+    resources.add(0x7fDEAD07);
+    resources.add(0x7fDEAD08);
+    resources.add(0x7fDEAD09);
+    resources.add(0x7fDEAD0a);
+    for (int id : resources) {
+      try {
+        getResources().getResourceName(id);
+      } catch (Resources.NotFoundException e) {
+        // Do nothing
+      }
+    }
   }
 }
diff --git a/tools/archive.py b/tools/archive.py
index 3eb0200..a3e15ba 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -13,7 +13,6 @@
 except ImportError:
   # Not a Unix system. Do what Gandalf tells you not to.
   pass
-import resource
 import shutil
 import subprocess
 import sys
diff --git a/tools/gradle.py b/tools/gradle.py
index 0e926e6..a52e3f3 100755
--- a/tools/gradle.py
+++ b/tools/gradle.py
@@ -41,6 +41,7 @@
 def GetJavaEnv(env):
   java_env = dict(env if env else os.environ, JAVA_HOME = jdk.GetJdkHome())
   java_env['PATH'] = java_env['PATH'] + os.pathsep + os.path.join(jdk.GetJdkHome(), 'bin')
+  java_env['GRADLE_OPTS'] = '-Xmx1g'
   return java_env
 
 def PrintCmd(s):