Merge commit '6a1de57911d923d0ed10404e4e00ee5df5e807ec' into dev-release
diff --git a/build.gradle b/build.gradle
index be64cba..33dfc26 100644
--- a/build.gradle
+++ b/build.gradle
@@ -293,6 +293,7 @@
 def r8LibGeneratedKeepRulesPath = "$buildDir/generated/keep.txt"
 def r8LibTestPath = "$buildDir/classes/r8libtest"
 def java11ClassFiles = "$buildDir/classes/java/mainJava11"
+def r8RetracePath = "$buildDir/libs/r8retrace.jar"
 
 def osString = OperatingSystem.current().isLinux() ? "linux" :
         OperatingSystem.current().isMacOsX() ? "mac" : "windows"
@@ -1070,6 +1071,17 @@
     outputs.file r8DesugaredPath
 }
 
+task R8Retrace {
+    dependsOn R8Lib
+    dependsOn r8LibCreateTask(
+            "Retrace",
+            ["src/main/keep_retrace.txt"],
+            R8Lib,
+            r8RetracePath,
+    ).dependsOn(R8Lib)
+    outputs.file r8RetracePath
+}
+
 task sourceJar(type: Jar, dependsOn: classes) {
     classifier = 'src'
     from sourceSets.main.allSource
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index cae27a3..4def5d0 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -40,6 +40,7 @@
 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.StringUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
@@ -142,8 +143,7 @@
    */
   public static void main(String[] args) {
     if (args.length == 0) {
-      System.err.println(USAGE_MESSAGE);
-      System.exit(ExceptionUtils.STATUS_ERROR);
+      throw new RuntimeException(StringUtils.joinLines("Invalid invocation.", USAGE_MESSAGE));
     }
     ExceptionUtils.withMainProgramHandler(() -> run(args));
   }
@@ -183,6 +183,12 @@
       AppView<AppInfo> appView = readApp(inputApp, options, executor, timing);
       SyntheticItems.collectSyntheticInputs(appView);
 
+      if (!options.mainDexKeepRules.isEmpty()) {
+        new GenerateMainDexList(options)
+            .traceMainDex(
+                executor, appView.appInfo().app(), appView.appInfo().getMainDexClasses()::addAll);
+      }
+
       final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
 
       if (AssertionsRewriter.isEnabled(options)) {
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index f720ee9..047df78 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -13,6 +13,11 @@
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.ProguardConfigurationParser;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.shaking.ProguardConfigurationSource;
+import com.android.tools.r8.shaking.ProguardConfigurationSourceFile;
+import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AssertionConfigurationWithDefault;
@@ -21,7 +26,11 @@
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.function.BiPredicate;
@@ -75,6 +84,7 @@
     private boolean enableMainDexListCheck = true;
     private boolean minimalMainDex = false;
     private boolean skipDump = false;
+    private final List<ProguardConfigurationSource> mainDexRules = new ArrayList<>();
 
     private Builder() {
       this(new DefaultD8DiagnosticsHandler());
@@ -203,6 +213,26 @@
       return self();
     }
 
+    /** Add proguard configuration files with rules for automatic main-dex-list calculation. */
+    public Builder addMainDexRulesFiles(Path... paths) {
+      return addMainDexRulesFiles(Arrays.asList(paths));
+    }
+
+    /** Add proguard configuration files with rules for automatic main-dex-list calculation. */
+    public Builder addMainDexRulesFiles(Collection<Path> paths) {
+      guard(() -> paths.forEach(p -> mainDexRules.add(new ProguardConfigurationSourceFile(p))));
+      return self();
+    }
+
+    /** Add proguard rules for automatic main-dex-list calculation. */
+    public Builder addMainDexRules(List<String> lines, Origin origin) {
+      guard(
+          () ->
+              mainDexRules.add(
+                  new ProguardConfigurationSourceStrings(lines, Paths.get("."), origin)));
+      return self();
+    }
+
     @Override
     void validate() {
       if (isPrintHelp()) {
@@ -219,8 +249,20 @@
         if (getProgramConsumer() instanceof DexFilePerClassFileConsumer) {
           reporter.error("Option --main-dex-list cannot be used with --file-per-class");
         }
-      } else if (getMainDexListConsumer() != null) {
-        reporter.error("Option --main-dex-list-output require --main-dex-list");
+      }
+      if (!mainDexRules.isEmpty()) {
+        if (intermediate) {
+          reporter.error("Option --main-dex-rules cannot be used with --intermediate");
+        }
+        if (getProgramConsumer() instanceof DexFilePerClassFileConsumer) {
+          reporter.error("Option --main-dex-rules cannot be used with --file-per-class");
+        }
+      }
+      if (getMainDexListConsumer() != null
+          && mainDexRules.isEmpty()
+          && !getAppBuilder().hasMainDexList()) {
+        reporter.error(
+            "Option --main-dex-list-output requires --main-dex-rules and/or --main-dex-list");
       }
       if (getMinApiLevel() >= AndroidApiLevel.L.getLevel()) {
         if (getMainDexListConsumer() != null || getAppBuilder().hasMainDexList()) {
@@ -248,6 +290,9 @@
       DesugaredLibraryConfiguration libraryConfiguration =
           getDesugaredLibraryConfiguration(factory, false);
 
+      ImmutableList<ProguardConfigurationRule> mainDexKeepRules =
+          ProguardConfigurationParser.parse(mainDexRules, factory, getReporter());
+
       return new D8Command(
           getAppBuilder().build(),
           getMode(),
@@ -269,6 +314,7 @@
           skipDump,
           enableMainDexListCheck,
           minimalMainDex,
+          mainDexKeepRules,
           getThreadCount(),
           factory);
     }
@@ -284,6 +330,7 @@
   private final boolean skipDump;
   private final boolean enableMainDexListCheck;
   private final boolean minimalMainDex;
+  private final ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
   private final DexItemFactory factory;
 
   public static Builder builder() {
@@ -347,6 +394,7 @@
       boolean skipDump,
       boolean enableMainDexListCheck,
       boolean minimalMainDex,
+      ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
       int threadCount,
       DexItemFactory factory) {
     super(
@@ -371,6 +419,7 @@
     this.skipDump = skipDump;
     this.enableMainDexListCheck = enableMainDexListCheck;
     this.minimalMainDex = minimalMainDex;
+    this.mainDexKeepRules = mainDexKeepRules;
     this.factory = factory;
   }
 
@@ -384,6 +433,7 @@
     skipDump = false;
     enableMainDexListCheck = true;
     minimalMainDex = false;
+    mainDexKeepRules = null;
     factory = null;
   }
 
@@ -403,6 +453,7 @@
     internal.intermediate = intermediate;
     internal.readCompileTimeAnnotations = intermediate;
     internal.desugarGraphConsumer = desugarGraphConsumer;
+    internal.mainDexKeepRules = mainDexKeepRules;
 
     // Assert and fixup defaults.
     assert !internal.isShrinking();
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index d705c8e..8f90ade 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -31,6 +31,7 @@
           "--classpath",
           "--pg-map",
           MIN_API_FLAG,
+          "--main-dex-rules",
           "--main-dex-list",
           "--main-dex-list-output",
           "--desugared-lib",
@@ -103,9 +104,9 @@
     D8Command command = parse(args, Origin.root()).build();
     if (command.isPrintHelp()) {
       System.out.println(USAGE_MESSAGE);
-      System.exit(1);
+    } else {
+      D8.run(command);
     }
-    D8.run(command);
   }
 
   static final String USAGE_MESSAGE =
@@ -139,6 +140,8 @@
                   "  --no-desugaring         # Force disable desugaring.",
                   "  --desugared-lib <file>  # Specify desugared library configuration.",
                   "                          # <file> is a desugared library configuration (json).",
+                  "  --main-dex-rules <file> # Proguard keep rules for classes to place in the",
+                  "                          # primary dex file.",
                   "  --main-dex-list <file>  # List of classes to place in the primary dex file.",
                   "  --main-dex-list-output <file>",
                   "                          # Output resulting main dex list in <file>."),
@@ -252,6 +255,8 @@
         } catch (IOException e) {
           builder.error(new ExceptionDiagnostic(e, new PathOrigin(file)));
         }
+      } else if (arg.equals("--main-dex-rules")) {
+        builder.addMainDexRulesFiles(Paths.get(nextArg));
       } else if (arg.equals("--main-dex-list")) {
         builder.addMainDexListFiles(Paths.get(nextArg));
       } else if (arg.equals("--main-dex-list-output")) {
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index da40e79..740984c 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -46,6 +46,7 @@
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.Sets;
 import java.io.File;
@@ -794,9 +795,10 @@
       new GenerateLintFiles(args[1], args[2], args[3]).generateDesugaredLibraryApisDocumetation();
       return;
     }
-    System.out.println(
-        "Usage: GenerateLineFiles [--generate-api-docs] "
-            + "<desugar configuration> <desugar implementation> <output directory>");
-    System.exit(1);
+    throw new RuntimeException(
+        StringUtils.joinLines(
+            "Invalid invocation.",
+            "Usage: GenerateLineFiles [--generate-api-docs] "
+                + "<desugar configuration> <desugar implementation> <output directory>"));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 04479b1..d260bcb 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.AppInfoWithClassHierarchy;
 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.graph.SubtypingInfo;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerFactory;
@@ -33,6 +33,7 @@
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 @Keep
@@ -40,78 +41,91 @@
   private final Timing timing = new Timing("maindex");
   private final InternalOptions options;
 
-  private GenerateMainDexList(InternalOptions options) {
+  private List<String> result = null;
+
+  public GenerateMainDexList(InternalOptions options) {
     this.options = options;
   }
 
   private List<String> run(AndroidApp app, ExecutorService executor)
       throws IOException {
     try {
-      DirectMappedDexApplication application =
-          new ApplicationReader(app, options, timing).read(executor).toDirect();
-      AppView<? extends AppInfoWithClassHierarchy> appView = AppView.createForR8(application);
-      appView.setAppServices(AppServices.builder(appView).build());
+      DexApplication application = new ApplicationReader(app, options, timing).read(executor);
+      traceMainDex(
+          executor,
+          application,
+          mainDexTracingResult -> {
+            result =
+                mainDexTracingResult.getClasses().stream()
+                    .map(c -> c.toSourceString().replace('.', '/') + ".class")
+                    .sorted()
+                    .collect(Collectors.toList());
 
-      MainDexListBuilder.checkForAssumedLibraryTypes(appView.appInfo());
-
-      SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
-
-      RootSet mainDexRootSet =
-          new RootSetBuilder(appView, subtypingInfo, options.mainDexKeepRules).run(executor);
-
-      GraphConsumer graphConsumer = options.mainDexKeptGraphConsumer;
-      WhyAreYouKeepingConsumer whyAreYouKeepingConsumer = null;
-      if (!mainDexRootSet.reasonAsked.isEmpty()) {
-        whyAreYouKeepingConsumer = new WhyAreYouKeepingConsumer(graphConsumer);
-        graphConsumer = whyAreYouKeepingConsumer;
-      }
-
-      Enqueuer enqueuer =
-          EnqueuerFactory.createForMainDexTracing(appView, subtypingInfo, graphConsumer);
-      Set<DexProgramClass> liveTypes = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
-      // LiveTypes is the result.
-      MainDexTracingResult mainDexTracingResult = new MainDexListBuilder(liveTypes, appView).run();
-
-      List<String> result =
-          mainDexTracingResult.getClasses().stream()
-              .map(c -> c.toSourceString().replace('.', '/') + ".class")
-              .sorted()
-              .collect(Collectors.toList());
-
-      if (options.mainDexListConsumer != null) {
-        options.mainDexListConsumer.accept(String.join("\n", result), options.reporter);
-        options.mainDexListConsumer.finished(options.reporter);
-      }
-
-      R8.processWhyAreYouKeepingAndCheckDiscarded(
-          mainDexRootSet,
-          () -> {
-            ArrayList<DexProgramClass> classes = new ArrayList<>();
-            // TODO(b/131668850): This is not a deterministic order!
-            mainDexTracingResult
-                .getClasses()
-                .forEach(
-                    type -> {
-                      DexClass clazz = appView.definitionFor(type);
-                      assert clazz.isProgramClass();
-                      classes.add(clazz.asProgramClass());
-                    });
-            return classes;
-          },
-          whyAreYouKeepingConsumer,
-          appView,
-          enqueuer,
-          true,
-          options,
-          timing,
-          executor);
-
+            if (options.mainDexListConsumer != null) {
+              options.mainDexListConsumer.accept(String.join("\n", result), options.reporter);
+              options.mainDexListConsumer.finished(options.reporter);
+            }
+          });
       return result;
     } catch (ExecutionException e) {
       throw unwrapExecutionException(e);
     }
   }
 
+  public void traceMainDex(
+      ExecutorService executor,
+      DexApplication application,
+      Consumer<MainDexTracingResult> resultConsumer)
+      throws ExecutionException {
+    AppView<? extends AppInfoWithClassHierarchy> appView =
+        AppView.createForR8(application.toDirect());
+    appView.setAppServices(AppServices.builder(appView).build());
+
+    MainDexListBuilder.checkForAssumedLibraryTypes(appView.appInfo());
+
+    SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
+
+    RootSet mainDexRootSet =
+        new RootSetBuilder(appView, subtypingInfo, options.mainDexKeepRules).run(executor);
+
+    GraphConsumer graphConsumer = options.mainDexKeptGraphConsumer;
+    WhyAreYouKeepingConsumer whyAreYouKeepingConsumer = null;
+    if (!mainDexRootSet.reasonAsked.isEmpty()) {
+      whyAreYouKeepingConsumer = new WhyAreYouKeepingConsumer(graphConsumer);
+      graphConsumer = whyAreYouKeepingConsumer;
+    }
+
+    Enqueuer enqueuer =
+        EnqueuerFactory.createForMainDexTracing(appView, subtypingInfo, graphConsumer);
+    Set<DexProgramClass> liveTypes = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
+    // LiveTypes is the result.
+    MainDexTracingResult mainDexTracingResult = new MainDexListBuilder(liveTypes, appView).run();
+    resultConsumer.accept(mainDexTracingResult);
+
+    R8.processWhyAreYouKeepingAndCheckDiscarded(
+        mainDexRootSet,
+        () -> {
+          ArrayList<DexProgramClass> classes = new ArrayList<>();
+          // TODO(b/131668850): This is not a deterministic order!
+          mainDexTracingResult
+              .getClasses()
+              .forEach(
+                  type -> {
+                    DexClass clazz = appView.definitionFor(type);
+                    assert clazz.isProgramClass();
+                    classes.add(clazz.asProgramClass());
+                  });
+          return classes;
+        },
+        whyAreYouKeepingConsumer,
+        appView,
+        enqueuer,
+        true,
+        options,
+        timing,
+        executor);
+  }
+
   /**
    * Main API entry for computing the main-dex list.
    *
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
index 07569a7..b08a738 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -108,15 +108,8 @@
         return new GenerateMainDexListCommand(isPrintHelp(), isPrintVersion());
       }
 
-      List<ProguardConfigurationRule> mainDexKeepRules;
-      if (this.mainDexRules.isEmpty()) {
-        mainDexKeepRules = ImmutableList.of();
-      } else {
-        ProguardConfigurationParser parser =
-            new ProguardConfigurationParser(factory, getReporter());
-        parser.parse(mainDexRules);
-        mainDexKeepRules = parser.getConfig().getRules();
-      }
+      List<ProguardConfigurationRule> mainDexKeepRules =
+          ProguardConfigurationParser.parse(mainDexRules, factory, getReporter());
 
       return new GenerateMainDexListCommand(
           factory,
diff --git a/src/main/java/com/android/tools/r8/JarDiff.java b/src/main/java/com/android/tools/r8/JarDiff.java
index 817b461..a268496 100644
--- a/src/main/java/com/android/tools/r8/JarDiff.java
+++ b/src/main/java/com/android/tools/r8/JarDiff.java
@@ -5,15 +5,14 @@
 
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.graph.ClassKind;
-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.JarApplicationReader;
 import com.android.tools.r8.graph.JarClassFileReader;
 import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StreamUtils;
-import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -21,7 +20,6 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
-import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 /**
@@ -136,26 +134,11 @@
         inputJar.getProgramResource(descriptor).getByteStream());
   }
 
-  private DexProgramClass getDexProgramClass(Path path, byte[] bytes) throws IOException {
-
-    class Collector implements Consumer<DexClass> {
-
-      private DexClass dexClass;
-
-      @Override
-      public void accept(DexClass dexClass) {
-        this.dexClass = dexClass;
-      }
-
-      public DexClass get() {
-        assert dexClass != null;
-        return dexClass;
-      }
-    }
-
-    Collector collector = new Collector();
-    JarClassFileReader reader = new JarClassFileReader(applicationReader, collector);
-    reader.read(new PathOrigin(path), ClassKind.PROGRAM, bytes);
+  private DexProgramClass getDexProgramClass(Path path, byte[] bytes) {
+    Box<DexProgramClass> collector = new Box<>();
+    JarClassFileReader<DexProgramClass> reader =
+        new JarClassFileReader<>(applicationReader, collector::set, ClassKind.PROGRAM);
+    reader.read(new PathOrigin(path), bytes);
     return collector.get().asProgramClass();
   }
 
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 40f48d2..f610d83 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SelfRetraceTest;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
@@ -186,8 +187,7 @@
    */
   public static void main(String[] args) {
     if (args.length == 0) {
-      System.err.println(USAGE_MESSAGE);
-      System.exit(ExceptionUtils.STATUS_ERROR);
+      throw new RuntimeException(StringUtils.joinLines("Invalid invocation.", USAGE_MESSAGE));
     }
     ExceptionUtils.withMainProgramHandler(() -> run(args));
   }
diff --git a/src/main/java/com/android/tools/r8/L8CommandParser.java b/src/main/java/com/android/tools/r8/L8CommandParser.java
index 0a57389..3dac9dc 100644
--- a/src/main/java/com/android/tools/r8/L8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/L8CommandParser.java
@@ -25,9 +25,9 @@
     L8Command command = parse(args, Origin.root()).build();
     if (command.isPrintHelp()) {
       System.out.println(USAGE_MESSAGE);
-      System.exit(1);
+    } else {
+      L8.run(command);
     }
-    L8.run(command);
   }
 
   static final String USAGE_MESSAGE =
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
index b8a426c..212eb32 100644
--- a/src/main/java/com/android/tools/r8/PrintSeeds.java
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 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 com.android.tools.r8.utils.Timing;
 import java.io.IOException;
@@ -39,21 +40,19 @@
 public class PrintSeeds {
 
   private static final String USAGE =
-      "Arguments: <rt.jar> <r8.jar> <pg-conf.txt>\n"
-          + "\n"
-          + "PrintSeeds prints the classes, interfaces, methods and fields selected by\n"
-          + "<pg-conf.txt> when compiling <r8.jar> alongside <rt.jar>.\n"
-          + "\n"
-          + "The output format is identical to what is printed when -printseeds is specified in\n"
-          + "<pg-conf.txt>, but running PrintSeeds can be faster than running R8 with \n"
-          + "-printseeds. See also the "
-          + PrintUses.class.getSimpleName()
-          + " program in R8.";
+      StringUtils.joinLines(
+          "Arguments: <rt.jar> <r8.jar> <pg-conf.txt>",
+          "",
+          "PrintSeeds prints the classes, interfaces, methods and fields selected by",
+          "<pg-conf.txt> when compiling <r8.jar> alongside <rt.jar>.",
+          "",
+          "The output format is identical to what is printed when -printseeds is specified in",
+          "<pg-conf.txt>, but running PrintSeeds can be faster than running R8 with",
+          "-printseeds. See also the " + PrintUses.class.getSimpleName() + " program in R8.");
 
   public static void main(String[] args) throws Exception {
     if (args.length != 3) {
-      System.out.println(USAGE.replace("\n", System.lineSeparator()));
-      System.exit(1);
+      throw new RuntimeException(StringUtils.joinLines("Invalid invocation.", USAGE));
     }
     Path rtJar = Paths.get(args[0]);
     Path r8Jar = Paths.get(args[1]);
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 3d039c9..ef8708f 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -12,6 +12,7 @@
 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.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -60,17 +61,18 @@
 public class PrintUses {
 
   private static final String USAGE =
-      "Arguments: [--keeprules, --keeprules-allowobfuscation] <rt.jar> <r8.jar> <sample.jar>\n"
-          + "\n"
-          + "PrintUses prints the classes, interfaces, methods and fields used by <sample.jar>,\n"
-          + "restricted to classes and interfaces in <r8.jar> that are not in <sample.jar>.\n"
-          + "<rt.jar> and <r8.jar> should point to libraries used by <sample.jar>.\n"
-          + "\n"
-          + "The output is in the same format as what is printed when specifying -printseeds in\n"
-          + "a ProGuard configuration file. Use --keeprules or --keeprules-allowobfuscation for "
-          + "outputting proguard keep rules. See also the "
-          + PrintSeeds.class.getSimpleName()
-          + " program in R8.";
+      StringUtils.joinLines(
+          "Arguments: [--keeprules, --keeprules-allowobfuscation] <rt.jar> <r8.jar> <sample.jar>",
+          "",
+          "PrintUses prints the classes, interfaces, methods and fields used by <sample.jar>,",
+          "restricted to classes and interfaces in <r8.jar> that are not in <sample.jar>.",
+          "<rt.jar> and <r8.jar> should point to libraries used by <sample.jar>.",
+          "",
+          "The output is in the same format as what is printed when specifying -printseeds in",
+          "a ProGuard configuration file. Use --keeprules or --keeprules-allowobfuscation for",
+          "outputting proguard keep rules. See also the "
+              + PrintSeeds.class.getSimpleName()
+              + " program in R8.");
 
   private final Set<String> descriptors;
   private final Printer printer;
@@ -137,9 +139,9 @@
 
     @Override
     public void registerInvokeSuper(DexMethod method) {
-      DexEncodedMethod superTarget = appInfo.lookupSuperTarget(method, context);
+      DexClassAndMethod superTarget = appInfo.lookupSuperTarget(method, context);
       if (superTarget != null) {
-        addMethod(superTarget.method);
+        addMethod(superTarget.getReference());
       } else {
         addMethod(method);
       }
@@ -247,12 +249,12 @@
     }
 
     private void registerMethod(ProgramMethod method) {
-      DexEncodedMethod superTarget =
+      DexClassAndMethod superTarget =
           appInfo
               .resolveMethodOn(method.getHolder(), method.getReference())
               .lookupInvokeSpecialTarget(context, appInfo);
       if (superTarget != null) {
-        addMethod(superTarget.method);
+        addMethod(superTarget.getReference());
       }
       for (DexType type : method.getDefinition().parameters().values) {
         registerTypeReference(type);
@@ -305,8 +307,7 @@
 
   public static void main(String... args) throws Exception {
     if (args.length != 3 && args.length != 4 && args.length != 5) {
-      System.out.println(USAGE.replace("\n", System.lineSeparator()));
-      return;
+      throw new RuntimeException(StringUtils.joinLines("Invalid invocation.", USAGE));
     }
     int argumentIndex = 0;
     boolean printKeep = false;
@@ -318,9 +319,9 @@
       // Make sure there is only one argument that mentions --keeprules
       for (int i = 1; i < args.length; i++) {
         if (args[i].startsWith("-keeprules")) {
-          System.out.println("Use either --keeprules or --keeprules-allowobfuscation, not both.");
-          System.out.println(USAGE.replace("\n", System.lineSeparator()));
-          return;
+          throw new RuntimeException(
+              StringUtils.joinLines(
+                  "Use either --keeprules or --keeprules-allowobfuscation, not both.", USAGE));
         }
       }
     }
@@ -342,8 +343,7 @@
     printUses.analyze();
     printUses.print();
     if (printUses.errors > 0) {
-      System.err.println(printUses.errors + " errors");
-      System.exit(1);
+      throw new RuntimeException(printUses.errors + " errors");
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 1dc771e..9e7a967 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -117,6 +117,7 @@
 import com.android.tools.r8.utils.LineNumberOptimizer;
 import com.android.tools.r8.utils.SelfRetraceTest;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
@@ -1130,8 +1131,7 @@
    */
   public static void main(String[] args) {
     if (args.length == 0) {
-      System.err.println(USAGE_MESSAGE);
-      System.exit(ExceptionUtils.STATUS_ERROR);
+      throw new RuntimeException(StringUtils.joinLines("Invalid invocation.", USAGE_MESSAGE));
     }
     ExceptionUtils.withMainProgramHandler(() -> run(args));
   }
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 29112ed..d6ab3a6 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -40,6 +40,7 @@
 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.Objects;
@@ -182,28 +183,21 @@
 
     /** Add proguard configuration files with rules for automatic main-dex-list calculation. */
     public Builder addMainDexRulesFiles(Path... paths) {
-      guard(() -> {
-        for (Path path : paths) {
-          mainDexRules.add(new ProguardConfigurationSourceFile(path));
-        }
-      });
-      return self();
+      return addMainDexRulesFiles(Arrays.asList(paths));
     }
 
     /** Add proguard configuration files with rules for automatic main-dex-list calculation. */
     public Builder addMainDexRulesFiles(Collection<Path> paths) {
-      guard(() -> {
-        for (Path path : paths) {
-          mainDexRules.add(new ProguardConfigurationSourceFile(path));
-        }
-      });
+      guard(() -> paths.forEach(p -> mainDexRules.add(new ProguardConfigurationSourceFile(p))));
       return self();
     }
 
     /** Add proguard rules for automatic main-dex-list calculation. */
     public Builder addMainDexRules(List<String> lines, Origin origin) {
-      guard(() -> mainDexRules.add(
-          new ProguardConfigurationSourceStrings(lines, Paths.get("."), origin)));
+      guard(
+          () ->
+              mainDexRules.add(
+                  new ProguardConfigurationSourceStrings(lines, Paths.get("."), origin)));
       return self();
     }
 
@@ -432,7 +426,7 @@
           && mainDexRules.isEmpty()
           && !getAppBuilder().hasMainDexList()) {
         reporter.error(
-            "Option --main-dex-list-output require --main-dex-rules and/or --main-dex-list");
+            "Option --main-dex-list-output requires --main-dex-rules and/or --main-dex-list");
       }
       if (!(getProgramConsumer() instanceof ClassFileConsumer)
           && getMinApiLevel() >= AndroidApiLevel.L.getLevel()) {
@@ -479,15 +473,8 @@
     private R8Command makeR8Command() {
       Reporter reporter = getReporter();
       DexItemFactory factory = new DexItemFactory();
-      List<ProguardConfigurationRule> mainDexKeepRules;
-      if (this.mainDexRules.isEmpty()) {
-        mainDexKeepRules = ImmutableList.of();
-      } else {
-        ProguardConfigurationParser parser =
-            new ProguardConfigurationParser(factory, reporter);
-        parser.parse(mainDexRules);
-        mainDexKeepRules = parser.getConfig().getRules();
-      }
+      List<ProguardConfigurationRule> mainDexKeepRules =
+          ProguardConfigurationParser.parse(mainDexRules, factory, reporter);
 
       DesugaredLibraryConfiguration libraryConfiguration =
           getDesugaredLibraryConfiguration(factory, false);
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index 3cb01e1..277bec8 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -42,9 +42,9 @@
     R8Command command = parse(args, Origin.root()).build();
     if (command.isPrintHelp()) {
       System.out.println(USAGE_MESSAGE);
-      System.exit(1);
+    } else {
+      R8.run(command);
     }
-    R8.run(command);
   }
 
   // Internal state to verify parsing properties not enforced by the builder.
diff --git a/src/main/java/com/android/tools/r8/ReadMainDexList.java b/src/main/java/com/android/tools/r8/ReadMainDexList.java
index de34761..4061e68 100644
--- a/src/main/java/com/android/tools/r8/ReadMainDexList.java
+++ b/src/main/java/com/android/tools/r8/ReadMainDexList.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.Iterators;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -40,8 +41,9 @@
 
   private void run(String[] args) throws Exception {
     if (args.length < 1 || args.length > 3) {
-      System.out.println("Usage: command [-k] <main_dex_list> [<proguard_map>]");
-      System.exit(0);
+      throw new RuntimeException(
+          StringUtils.joinLines(
+              "Invalid invocation.", "Usage: command [-k] <main_dex_list> [<proguard_map>]"));
     }
 
     Iterator<String> arguments = Iterators.forArray(args);
diff --git a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
index 177f2ae..24b42d3 100644
--- a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
+++ b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.Version;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.origin.CommandLineOrigin;
-import com.android.tools.r8.utils.AbortException;
+import com.android.tools.r8.utils.ExceptionUtils;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Paths;
 import java.util.List;
@@ -211,12 +211,6 @@
   }
 
   public static void main(String[] args) {
-    try {
-      run(args);
-    } catch (CompilationFailedException | AbortException e) {
-      // Detail of the errors were already reported
-      System.err.println("Compilation failed");
-      System.exit(1);
-    }
+    ExceptionUtils.withMainProgramHandler(() -> run(args));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index b46cd9b..4b035d4 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -151,7 +151,7 @@
       // TODO: try and preload less classes.
       readProguardMap(proguardMap, builder, executorService, futures);
       ClassReader classReader = new ClassReader(executorService, futures);
-      JarClassFileReader jcf = classReader.readSources();
+      JarClassFileReader<DexProgramClass> jcf = classReader.readSources();
       ThreadUtils.awaitFutures(futures);
       classReader.initializeLazyClassCollection(builder);
       for (ProgramResourceProvider provider : inputApp.getProgramResourceProviders()) {
@@ -296,46 +296,46 @@
       this.futures = futures;
     }
 
-    private <T extends DexClass> void readDexSources(
-        List<ProgramResource> dexSources, ClassKind classKind, Queue<T> classes)
+    private void readDexSources(List<ProgramResource> dexSources, Queue<DexProgramClass> classes)
         throws IOException, ResourceException {
       if (dexSources.size() > 0) {
-        List<DexParser> dexParsers = new ArrayList<>(dexSources.size());
+        List<DexParser<DexProgramClass>> dexParsers = new ArrayList<>(dexSources.size());
         int computedMinApiLevel = options.minApiLevel;
         for (ProgramResource input : dexSources) {
           DexReader dexReader = new DexReader(input);
           if (options.passthroughDexCode) {
             computedMinApiLevel = validateOrComputeMinApiLevel(computedMinApiLevel, dexReader);
           }
-          dexParsers.add(new DexParser(dexReader, classKind, options));
+          dexParsers.add(new DexParser<>(dexReader, PROGRAM, options));
         }
 
         options.minApiLevel = computedMinApiLevel;
-        for (DexParser dexParser : dexParsers) {
+        for (DexParser<DexProgramClass> dexParser : dexParsers) {
           dexParser.populateIndexTables();
         }
         // Read the DexCode items and DexProgramClass items in parallel.
         if (!options.skipReadingDexCode) {
-          for (DexParser dexParser : dexParsers) {
-            futures.add(executorService.submit(() -> {
-              dexParser.addClassDefsTo(
-                  classKind.bridgeConsumer(classes::add)); // Depends on Methods, Code items etc.
-            }));
+          for (DexParser<DexProgramClass> dexParser : dexParsers) {
+            futures.add(
+                executorService.submit(
+                    () -> {
+                      dexParser.addClassDefsTo(classes::add); // Depends on Methods, Code items etc.
+                    }));
           }
         }
       }
     }
 
-    private <T extends DexClass> JarClassFileReader readClassSources(
-        List<ProgramResource> classSources, ClassKind classKind, Queue<T> classes) {
-      JarClassFileReader reader = new JarClassFileReader(
-          application, classKind.bridgeConsumer(classes::add));
+    private JarClassFileReader<DexProgramClass> readClassSources(
+        List<ProgramResource> classSources, Queue<DexProgramClass> classes) {
+      JarClassFileReader<DexProgramClass> reader =
+          new JarClassFileReader<>(application, classes::add, PROGRAM);
       // Read classes in parallel.
       for (ProgramResource input : classSources) {
         futures.add(
             executorService.submit(
                 () -> {
-                  reader.read(input, classKind);
+                  reader.read(input);
                   // No other way to have a void callable, but we want the IOException from read
                   // to be wrapped into an ExecutionException.
                   return null;
@@ -344,7 +344,7 @@
       return reader;
     }
 
-    JarClassFileReader readSources() throws IOException, ResourceException {
+    JarClassFileReader<DexProgramClass> readSources() throws IOException, ResourceException {
       Collection<ProgramResource> resources = inputApp.computeAllProgramResources();
       List<ProgramResource> dexResources = new ArrayList<>(resources.size());
       List<ProgramResource> cfResources = new ArrayList<>(resources.size());
@@ -356,12 +356,14 @@
           cfResources.add(resource);
         }
       }
-      readDexSources(dexResources, PROGRAM, programClasses);
-      return readClassSources(cfResources, PROGRAM, programClasses);
+      readDexSources(dexResources, programClasses);
+      return readClassSources(cfResources, programClasses);
     }
 
-    private <T extends DexClass> ClassProvider<T> buildClassProvider(ClassKind classKind,
-        Queue<T> preloadedClasses, List<ClassFileResourceProvider> resourceProviders,
+    private <T extends DexClass> ClassProvider<T> buildClassProvider(
+        ClassKind<T> classKind,
+        Queue<T> preloadedClasses,
+        List<ClassFileResourceProvider> resourceProviders,
         JarApplicationReader reader) {
       List<ClassProvider<T>> providers = new ArrayList<>();
 
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 2408b27..cd78995 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -82,14 +82,14 @@
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
-public class DexParser {
+public class DexParser<T extends DexClass> {
 
   private final int NO_INDEX = -1;
   private final Origin origin;
   private DexReader dexReader;
   private final DexSection[] dexSections;
   private int[] stringIDs;
-  private final ClassKind classKind;
+  private final ClassKind<T> classKind;
   private final InternalOptions options;
   private Object2LongMap<String> checksums;
 
@@ -102,7 +102,8 @@
   }
 
   private static DexSection[] parseMapFrom(DexReader dexReader) {
-    DexParser dexParser = new DexParser(dexReader, ClassKind.PROGRAM, new InternalOptions());
+    DexParser<DexProgramClass> dexParser =
+        new DexParser<>(dexReader, ClassKind.PROGRAM, new InternalOptions());
     return dexParser.dexSections;
   }
 
@@ -127,7 +128,7 @@
   // Factory to canonicalize certain dexitems.
   private final DexItemFactory dexItemFactory;
 
-  public DexParser(DexReader dexReader, ClassKind classKind, InternalOptions options) {
+  public DexParser(DexReader dexReader, ClassKind<T> classKind, InternalOptions options) {
     assert dexReader.getOrigin() != null;
     this.origin = dexReader.getOrigin();
     this.dexReader = dexReader;
@@ -419,14 +420,14 @@
     return result;
   }
 
-  private <T> Object cacheAt(int offset, Supplier<T> function, Supplier<T> defaultValue) {
+  private <S> Object cacheAt(int offset, Supplier<S> function, Supplier<S> defaultValue) {
     if (offset == 0) {
       return defaultValue.get();
     }
     return cacheAt(offset, function);
   }
 
-  private <T> Object cacheAt(int offset, Supplier<T> function) {
+  private <S> Object cacheAt(int offset, Supplier<S> function) {
     if (offset == 0) {
       return null;  // return null for offset zero.
     }
@@ -704,7 +705,7 @@
     return methods;
   }
 
-  void addClassDefsTo(Consumer<DexClass> classCollection) {
+  void addClassDefsTo(Consumer<T> classCollection) {
     final DexSection dexSection = lookupSection(Constants.TYPE_CLASS_DEF_ITEM);
     final int length = dexSection.length;
     indexedItems.initializeClasses(length);
@@ -796,7 +797,7 @@
       ChecksumSupplier checksumSupplier =
           finalChecksum == null ? DexProgramClass::invalidChecksumRequest : c -> finalChecksum;
 
-      DexClass clazz =
+      T clazz =
           classKind.create(
               type,
               Kind.DEX,
@@ -925,7 +926,7 @@
           int hsize = dexReader.getSleb128();
           int realHsize = Math.abs(hsize);
           // - handlers	encoded_type_addr_pair[abs(size)]
-          TryHandler.TypeAddrPair pairs[] = new TryHandler.TypeAddrPair[realHsize];
+          TryHandler.TypeAddrPair[] pairs = new TryHandler.TypeAddrPair[realHsize];
           for (int j = 0; j < realHsize; j++) {
             int typeIdx = dexReader.getUleb128();
             int addr = dexReader.getUleb128();
@@ -1415,7 +1416,7 @@
           List<DexType> members = DexAnnotation.getMemberClassesFromAnnotation(annotation, factory);
           if (memberClasses == null) {
             memberClasses = members;
-          } else {
+          } else if (members != null) {
             memberClasses.addAll(members);
           }
         } else if (DexAnnotation.isSignatureAnnotation(annotation, factory)
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index bce34ff..917f0de 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -221,6 +221,7 @@
         graphLens,
         namingLens,
         initClassLens,
+        transaction.rewriter,
         indexedItems.classes,
         indexedItems.protos,
         indexedItems.types,
@@ -747,7 +748,7 @@
       this.graphLens = graphLens;
       this.initClassLens = initClassLens;
       this.namingLens = namingLens;
-      this.rewriter = new LensCodeRewriterUtils(appView);
+      this.rewriter = new LensCodeRewriterUtils(appView, true);
     }
 
     private <T extends DexItem> boolean maybeInsert(T item, Set<T> set, Set<T> baseSet) {
diff --git a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
index d9a5609..4ee416c 100644
--- a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
+++ b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
@@ -362,8 +362,7 @@
             run(args);
           } catch (FeatureMappingException e) {
             // TODO(ricow): Report feature mapping errors via the reporter.
-            System.err.println("Splitting failed: " + e.getMessage());
-            System.exit(1);
+            throw new RuntimeException("Splitting failed: " + e.getMessage());
           }
         });
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index 98806f6..70d7cdb 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -320,6 +320,33 @@
       return self();
     }
 
+    public B setPrivate(boolean value) {
+      if (value) {
+        flags.setPrivate();
+      } else {
+        flags.unsetPrivate();
+      }
+      return self();
+    }
+
+    public B setProtected(boolean value) {
+      if (value) {
+        flags.setProtected();
+      } else {
+        flags.unsetProtected();
+      }
+      return self();
+    }
+
+    public B setPublic(boolean value) {
+      if (value) {
+        flags.setPublic();
+      } else {
+        flags.unsetPublic();
+      }
+      return self();
+    }
+
     public B setStatic() {
       flags.setStatic();
       return self();
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 e747aa0..56b1a53 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -492,13 +492,13 @@
    * @return The actual target for {@code method} or {@code null} if none found.
    */
   // TODO(b/155968472): This should take a parameter `boolean isInterface` and use resolveMethod().
-  public DexEncodedMethod lookupSuperTarget(DexMethod method, DexProgramClass context) {
+  public DexClassAndMethod lookupSuperTarget(DexMethod method, DexProgramClass context) {
     assert checkIfObsolete();
     return unsafeResolveMethodDueToDexFormat(method).lookupInvokeSuperTarget(context, this);
   }
 
   // TODO(b/155968472): This should take a parameter `boolean isInterface` and use resolveMethod().
-  public DexEncodedMethod lookupSuperTarget(DexMethod method, ProgramMethod context) {
+  public DexClassAndMethod lookupSuperTarget(DexMethod method, ProgramMethod context) {
     return lookupSuperTarget(method, context.getHolder());
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 6bedacf..531c78a 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -122,9 +122,9 @@
       this.callSiteOptimizationInfoPropagator = null;
     }
 
-    this.libraryMemberOptimizer = new LibraryMemberOptimizer(this);
     this.libraryMethodSideEffectModelCollection =
         new LibraryMethodSideEffectModelCollection(dexItemFactory());
+    this.libraryMemberOptimizer = new LibraryMemberOptimizer(this);
 
     if (enableWholeProgramOptimizations() && options().protoShrinking().isProtoShrinkingEnabled()) {
       this.protoShrinker = new ProtoShrinker(withLiveness());
diff --git a/src/main/java/com/android/tools/r8/graph/ClassKind.java b/src/main/java/com/android/tools/r8/graph/ClassKind.java
index 3929cfb..baf5365 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassKind.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassKind.java
@@ -9,97 +9,97 @@
 import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.origin.Origin;
 import java.util.List;
-import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 /** Kind of the application class. Can be program, classpath or library. */
-public enum ClassKind {
-  PROGRAM(DexProgramClass::new, DexClass::isProgramClass),
-  CLASSPATH(
-      (type,
-          kind,
-          origin,
-          accessFlags,
-          superType,
-          interfaces,
-          sourceFile,
-          nestHost,
-          nestMembers,
-          enclosingMember,
-          innerClasses,
-          classSignature,
-          annotations,
-          staticFields,
-          instanceFields,
-          directMethods,
-          virtualMethods,
-          skipNameValidationForTesting,
-          checksumSupplier) -> {
-        return new DexClasspathClass(
-            type,
-            kind,
-            origin,
-            accessFlags,
-            superType,
-            interfaces,
-            sourceFile,
-            nestHost,
-            nestMembers,
-            enclosingMember,
-            innerClasses,
-            classSignature,
-            annotations,
-            staticFields,
-            instanceFields,
-            directMethods,
-            virtualMethods,
-            skipNameValidationForTesting);
-      },
-      DexClass::isClasspathClass),
-  LIBRARY(
-      (type,
-          kind,
-          origin,
-          accessFlags,
-          superType,
-          interfaces,
-          sourceFile,
-          nestHost,
-          nestMembers,
-          enclosingMember,
-          innerClasses,
-          classSignature,
-          annotations,
-          staticFields,
-          instanceFields,
-          directMethods,
-          virtualMethods,
-          skipNameValidationForTesting,
-          checksumSupplier) -> {
-        return new DexLibraryClass(
-            type,
-            kind,
-            origin,
-            accessFlags,
-            superType,
-            interfaces,
-            sourceFile,
-            nestHost,
-            nestMembers,
-            enclosingMember,
-            innerClasses,
-            classSignature,
-            annotations,
-            staticFields,
-            instanceFields,
-            directMethods,
-            virtualMethods,
-            skipNameValidationForTesting);
-      },
-      DexClass::isLibraryClass);
+public class ClassKind<C extends DexClass> {
+  public static ClassKind<DexProgramClass> PROGRAM =
+      new ClassKind<>(DexProgramClass::new, DexClass::isProgramClass);
+  public static ClassKind<DexClasspathClass> CLASSPATH =
+      new ClassKind<>(
+          (type,
+              kind,
+              origin,
+              accessFlags,
+              superType,
+              interfaces,
+              sourceFile,
+              nestHost,
+              nestMembers,
+              enclosingMember,
+              innerClasses,
+              classSignature,
+              annotations,
+              staticFields,
+              instanceFields,
+              directMethods,
+              virtualMethods,
+              skipNameValidationForTesting,
+              checksumSupplier) ->
+              new DexClasspathClass(
+                  type,
+                  kind,
+                  origin,
+                  accessFlags,
+                  superType,
+                  interfaces,
+                  sourceFile,
+                  nestHost,
+                  nestMembers,
+                  enclosingMember,
+                  innerClasses,
+                  classSignature,
+                  annotations,
+                  staticFields,
+                  instanceFields,
+                  directMethods,
+                  virtualMethods,
+                  skipNameValidationForTesting),
+          DexClass::isClasspathClass);
+  public static final ClassKind<DexLibraryClass> LIBRARY =
+      new ClassKind<>(
+          (type,
+              kind,
+              origin,
+              accessFlags,
+              superType,
+              interfaces,
+              sourceFile,
+              nestHost,
+              nestMembers,
+              enclosingMember,
+              innerClasses,
+              classSignature,
+              annotations,
+              staticFields,
+              instanceFields,
+              directMethods,
+              virtualMethods,
+              skipNameValidationForTesting,
+              checksumSupplier) ->
+              new DexLibraryClass(
+                  type,
+                  kind,
+                  origin,
+                  accessFlags,
+                  superType,
+                  interfaces,
+                  sourceFile,
+                  nestHost,
+                  nestMembers,
+                  enclosingMember,
+                  innerClasses,
+                  classSignature,
+                  annotations,
+                  staticFields,
+                  instanceFields,
+                  directMethods,
+                  virtualMethods,
+                  skipNameValidationForTesting),
+          DexClass::isLibraryClass);
 
-  private interface Factory {
-    DexClass create(
+  private interface Factory<C extends DexClass> {
+    C create(
         DexType type,
         Kind kind,
         Origin origin,
@@ -121,15 +121,15 @@
         ChecksumSupplier checksumSupplier);
   }
 
-  private final Factory factory;
+  private final Factory<C> factory;
   private final Predicate<DexClass> check;
 
-  ClassKind(Factory factory, Predicate<DexClass> check) {
+  ClassKind(Factory<C> factory, Predicate<DexClass> check) {
     this.factory = factory;
     this.check = check;
   }
 
-  public DexClass create(
+  public C create(
       DexType type,
       Kind kind,
       Origin origin,
@@ -174,12 +174,4 @@
   public boolean isOfKind(DexClass clazz) {
     return check.test(clazz);
   }
-
-  public <T extends DexClass> Consumer<DexClass> bridgeConsumer(Consumer<T> consumer) {
-    return clazz -> {
-      assert isOfKind(clazz);
-      @SuppressWarnings("unchecked") T specialized = (T) clazz;
-      consumer.accept(specialized);
-    };
-  }
 }
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 82faf34..70bad80 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -470,6 +470,15 @@
   }
 
   /** Find method in this class matching {@param method}. */
+  public DexClassAndMethod lookupClassMethod(DexMethod method) {
+    return toClassMethodOrNull(methodCollection.getMethod(method));
+  }
+
+  private DexClassAndMethod toClassMethodOrNull(DexEncodedMethod method) {
+    return method != null ? DexClassAndMethod.create(this, method) : null;
+  }
+
+  /** Find method in this class matching {@param method}. */
   public DexEncodedMethod lookupMethod(DexMethod method) {
     return methodCollection.getMethod(method);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 4acd029..4fcd92b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -330,6 +330,10 @@
     assert parameterAnnotationsList != null;
   }
 
+  public static DexEncodedMethod toMethodDefinitionOrNull(DexClassAndMethod method) {
+    return method != null ? method.getDefinition() : null;
+  }
+
   public boolean isDeprecated() {
     return deprecated;
   }
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 a93000e..385fb58 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -23,7 +23,6 @@
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.ReferenceTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
@@ -453,13 +452,16 @@
   public final StringBuildingMethods stringBufferMethods =
       new StringBuildingMethods(stringBufferType);
   public final BooleanMembers booleanMembers = new BooleanMembers();
+  public final ByteMembers byteMembers = new ByteMembers();
+  public final CharMembers charMembers = new CharMembers();
   public final FloatMembers floatMembers = new FloatMembers();
   public final IntegerMembers integerMembers = new IntegerMembers();
+  public final LongMembers longMembers = new LongMembers();
   public final ObjectsMethods objectsMethods = new ObjectsMethods();
   public final ObjectMembers objectMembers = new ObjectMembers();
+  public final ShortMembers shortMembers = new ShortMembers();
   public final StringMembers stringMembers = new StringMembers();
-  public final LongMembers longMembers = new LongMembers();
-  public final DoubleMethods doubleMethods = new DoubleMethods();
+  public final DoubleMembers doubleMembers = new DoubleMembers();
   public final ThrowableMethods throwableMethods = new ThrowableMethods();
   public final AssertionErrorMethods assertionErrorMethods = new AssertionErrorMethods();
   public final ClassMethods classMethods = new ClassMethods();
@@ -715,7 +717,22 @@
 
   public Set<DexType> libraryClassesWithoutStaticInitialization =
       ImmutableSet.of(
-          boxedBooleanType, enumType, npeType, objectType, stringBufferType, stringBuilderType);
+          boxedBooleanType,
+          boxedByteType,
+          boxedCharType,
+          boxedDoubleType,
+          boxedFloatType,
+          boxedIntType,
+          boxedLongType,
+          boxedNumberType,
+          boxedShortType,
+          boxedVoidType,
+          enumType,
+          npeType,
+          objectType,
+          stringBufferType,
+          stringBuilderType,
+          stringType);
 
   private boolean skipNameValidationForTesting = false;
 
@@ -731,35 +748,12 @@
     return dexMethod == metafactoryMethod || dexMethod == metafactoryAltMethod;
   }
 
-  public interface LibraryMembers {
+  public abstract static class LibraryMembers {
 
-    void forEachFinalField(Consumer<DexField> consumer);
+    public void forEachFinalField(Consumer<DexField> consumer) {}
   }
 
-  public class BooleanMembers implements LibraryMembers {
-
-    public final DexField FALSE = createField(boxedBooleanType, boxedBooleanType, "FALSE");
-    public final DexField TRUE = createField(boxedBooleanType, boxedBooleanType, "TRUE");
-    public final DexField TYPE = createField(boxedBooleanType, classType, "TYPE");
-
-    public final DexMethod booleanValue =
-        createMethod(boxedBooleanType, createProto(booleanType), "booleanValue");
-    public final DexMethod parseBoolean =
-        createMethod(boxedBooleanType, createProto(booleanType, stringType), "parseBoolean");
-    public final DexMethod valueOf =
-        createMethod(boxedBooleanType, createProto(boxedBooleanType, booleanType), "valueOf");
-
-    private BooleanMembers() {}
-
-    @Override
-    public void forEachFinalField(Consumer<DexField> consumer) {
-      consumer.accept(FALSE);
-      consumer.accept(TRUE);
-      consumer.accept(TYPE);
-    }
-  }
-
-  public class AndroidOsBuildMembers implements LibraryMembers {
+  public class AndroidOsBuildMembers extends LibraryMembers {
 
     public final DexField BOOTLOADER = createField(androidOsBuildType, stringType, "BOOTLOADER");
     public final DexField BRAND = createField(androidOsBuildType, stringType, "BRAND");
@@ -805,7 +799,7 @@
     }
   }
 
-  public class AndroidOsBuildVersionMembers implements LibraryMembers {
+  public class AndroidOsBuildVersionMembers extends LibraryMembers {
 
     public final DexField CODENAME = createField(androidOsBuildVersionType, stringType, "CODENAME");
     public final DexField RELEASE = createField(androidOsBuildVersionType, stringType, "RELEASE");
@@ -824,7 +818,7 @@
     }
   }
 
-  public class AndroidOsBundleMembers implements LibraryMembers {
+  public class AndroidOsBundleMembers extends LibraryMembers {
 
     public final DexField CREATOR =
         createField(androidOsBundleType, androidOsParcelableCreatorType, "CREATOR");
@@ -837,7 +831,7 @@
     }
   }
 
-  public class AndroidSystemOsConstantsMembers implements LibraryMembers {
+  public class AndroidSystemOsConstantsMembers extends LibraryMembers {
 
     public final DexField S_IRUSR = createField(androidSystemOsConstantsType, intType, "S_IRUSR");
     public final DexField S_IXUSR = createField(androidSystemOsConstantsType, intType, "S_IXUSR");
@@ -849,7 +843,7 @@
     }
   }
 
-  public class AndroidViewViewMembers implements LibraryMembers {
+  public class AndroidViewViewMembers extends LibraryMembers {
 
     public final DexField TRANSLATION_Z =
         createField(androidViewViewType, androidUtilPropertyType, "TRANSLATION_Z");
@@ -872,10 +866,54 @@
     }
   }
 
-  public class FloatMembers implements LibraryMembers {
+  public class BooleanMembers extends LibraryMembers {
+
+    public final DexField FALSE = createField(boxedBooleanType, boxedBooleanType, "FALSE");
+    public final DexField TRUE = createField(boxedBooleanType, boxedBooleanType, "TRUE");
+    public final DexField TYPE = createField(boxedBooleanType, classType, "TYPE");
+
+    public final DexMethod booleanValue =
+        createMethod(boxedBooleanType, createProto(booleanType), "booleanValue");
+    public final DexMethod parseBoolean =
+        createMethod(boxedBooleanType, createProto(booleanType, stringType), "parseBoolean");
+    public final DexMethod valueOf =
+        createMethod(boxedBooleanType, createProto(boxedBooleanType, booleanType), "valueOf");
+    public final DexMethod toString =
+        createMethod(boxedBooleanType, createProto(stringType), "toString");
+
+    private BooleanMembers() {}
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(FALSE);
+      consumer.accept(TRUE);
+      consumer.accept(TYPE);
+    }
+  }
+
+  public class ByteMembers extends LibraryMembers {
+
+    public final DexMethod toString =
+        createMethod(boxedByteType, createProto(stringType), "toString");
+
+    private ByteMembers() {}
+  }
+
+  public class CharMembers extends LibraryMembers {
+
+    public final DexMethod toString =
+        createMethod(boxedCharType, createProto(stringType), "toString");
+
+    private CharMembers() {}
+  }
+
+  public class FloatMembers extends LibraryMembers {
 
     public final DexField TYPE = createField(boxedFloatType, classType, "TYPE");
 
+    public final DexMethod toString =
+        createMethod(boxedFloatType, createProto(stringType), "toString");
+
     private FloatMembers() {}
 
     @Override
@@ -884,7 +922,7 @@
     }
   }
 
-  public class JavaIoFileMembers implements LibraryMembers {
+  public class JavaIoFileMembers extends LibraryMembers {
 
     public final DexField pathSeparator = createField(javaIoFileType, stringType, "pathSeparator");
     public final DexField separator = createField(javaIoFileType, stringType, "separator");
@@ -896,7 +934,7 @@
     }
   }
 
-  public class JavaMathBigIntegerMembers implements LibraryMembers {
+  public class JavaMathBigIntegerMembers extends LibraryMembers {
 
     public final DexField ONE = createField(javaMathBigIntegerType, javaMathBigIntegerType, "ONE");
     public final DexField ZERO =
@@ -909,7 +947,7 @@
     }
   }
 
-  public class JavaNioByteOrderMembers implements LibraryMembers {
+  public class JavaNioByteOrderMembers extends LibraryMembers {
 
     public final DexField LITTLE_ENDIAN =
         createField(javaNioByteOrderType, javaNioByteOrderType, "LITTLE_ENDIAN");
@@ -937,7 +975,7 @@
     }
   }
 
-  public class JavaUtilComparatorMembers implements LibraryMembers {
+  public class JavaUtilComparatorMembers extends LibraryMembers {
 
     public final DexField EMPTY_LIST =
         createField(javaUtilCollectionsType, javaUtilListType, "EMPTY_LIST");
@@ -951,7 +989,7 @@
     }
   }
 
-  public class JavaUtilConcurrentTimeUnitMembers implements LibraryMembers {
+  public class JavaUtilConcurrentTimeUnitMembers extends LibraryMembers {
 
     public final DexField DAYS =
         createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "DAYS");
@@ -980,7 +1018,7 @@
     }
   }
 
-  public class JavaUtilLocaleMembers implements LibraryMembers {
+  public class JavaUtilLocaleMembers extends LibraryMembers {
 
     public final DexField ENGLISH = createField(javaUtilLocaleType, javaUtilLocaleType, "ENGLISH");
     public final DexField ROOT = createField(javaUtilLocaleType, javaUtilLocaleType, "ROOT");
@@ -994,7 +1032,7 @@
     }
   }
 
-  public class JavaUtilLoggingLevelMembers implements LibraryMembers {
+  public class JavaUtilLoggingLevelMembers extends LibraryMembers {
 
     public final DexField CONFIG =
         createField(javaUtilLoggingLevelType, javaUtilLoggingLevelType, "CONFIG");
@@ -1020,11 +1058,13 @@
     }
   }
 
-  public class LongMembers implements LibraryMembers {
+  public class LongMembers extends LibraryMembers {
 
     public final DexField TYPE = createField(boxedLongType, classType, "TYPE");
 
     public final DexMethod compare;
+    public final DexMethod toString =
+        createMethod(boxedLongType, createProto(stringType), "toString");
 
     private LongMembers() {
       compare = createMethod(boxedLongDescriptor,
@@ -1037,11 +1077,14 @@
     }
   }
 
-  public class DoubleMethods {
+  public class DoubleMembers {
 
     public final DexMethod isNaN;
 
-    private DoubleMethods() {
+    public final DexMethod toString =
+        createMethod(boxedDoubleType, createProto(stringType), "toString");
+
+    private DoubleMembers() {
       isNaN =
           createMethod(
               boxedDoubleDescriptor,
@@ -1051,10 +1094,13 @@
     }
   }
 
-  public class IntegerMembers implements LibraryMembers {
+  public class IntegerMembers extends LibraryMembers {
 
     public final DexField TYPE = createField(boxedIntType, classType, "TYPE");
 
+    public final DexMethod toString =
+        createMethod(boxedIntType, createProto(stringType), "toString");
+
     @Override
     public void forEachFinalField(Consumer<DexField> consumer) {
       consumer.accept(TYPE);
@@ -1445,7 +1491,15 @@
     }
   }
 
-  public class StringMembers implements LibraryMembers {
+  public class ShortMembers extends LibraryMembers {
+
+    public final DexMethod toString =
+        createMethod(boxedShortType, createProto(stringType), "toString");
+
+    private ShortMembers() {}
+  }
+
+  public class StringMembers extends LibraryMembers {
 
     public final DexField CASE_INSENSITIVE_ORDER =
         createField(stringType, javaUtilComparatorType, "CASE_INSENSITIVE_ORDER");
@@ -1454,6 +1508,8 @@
     public final DexMethod length;
 
     public final DexMethod concat;
+    public final DexMethod constructor =
+        createMethod(stringType, createProto(voidType, stringType), constructorMethodName);
     public final DexMethod contains;
     public final DexMethod startsWith;
     public final DexMethod endsWith;
@@ -1625,22 +1681,22 @@
       return constructorMethods.contains(method);
     }
 
-    public boolean constructorInvokeIsSideEffectFree(InvokeMethod invoke) {
-      DexMethod invokedMethod = invoke.getInvokedMethod();
-      if (invokedMethod == charSequenceConstructor) {
-        // Performs callbacks on the given CharSequence, which may have side effects.
-        TypeElement charSequenceType = invoke.getArgument(1).getType();
-        return charSequenceType.isClassType()
-            && charSequenceType.asClassType().getClassType() == stringType;
-      }
-
+    public boolean constructorInvokeIsSideEffectFree(
+        DexMethod invokedMethod, List<Value> arguments) {
       if (invokedMethod == defaultConstructor) {
         return true;
       }
 
+      if (invokedMethod == charSequenceConstructor) {
+        // Performs callbacks on the given CharSequence, which may have side effects.
+        TypeElement charSequenceType = arguments.get(1).getType();
+        return charSequenceType.isClassType()
+            && charSequenceType.asClassType().getClassType() == stringType;
+      }
+
       if (invokedMethod == intConstructor) {
         // NegativeArraySizeException - if the capacity argument is less than 0.
-        Value capacityValue = invoke.inValues().get(1);
+        Value capacityValue = arguments.get(1);
         if (capacityValue.hasValueRange()) {
           return capacityValue.getValueRange().getMin() >= 0;
         }
@@ -1649,7 +1705,7 @@
 
       if (invokedMethod == stringConstructor) {
         // NullPointerException - if str is null.
-        Value strValue = invoke.inValues().get(1);
+        Value strValue = arguments.get(1);
         return !strValue.getType().isNullable();
       }
 
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index fabdde7..88596f9 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -66,10 +66,8 @@
 import org.objectweb.asm.Type;
 import org.objectweb.asm.TypePath;
 
-/**
- * Java/Jar class reader for constructing dex/graph structure.
- */
-public class JarClassFileReader {
+/** Java/Jar class reader for constructing dex/graph structure. */
+public class JarClassFileReader<T extends DexClass> {
 
   private static final byte[] CLASSFILE_HEADER = ByteBuffer.allocate(4).putInt(0xCAFEBABE).array();
 
@@ -77,24 +75,25 @@
   private static final int ACC_SYNTHETIC_ATTRIBUTE = 0x40000;
 
   private final JarApplicationReader application;
-  private final Consumer<DexClass> classConsumer;
+  private final Consumer<T> classConsumer;
+  private final ClassKind<T> classKind;
 
   public JarClassFileReader(
-      JarApplicationReader application, Consumer<DexClass> classConsumer) {
+      JarApplicationReader application, Consumer<T> classConsumer, ClassKind<T> classKind) {
     this.application = application;
     this.classConsumer = classConsumer;
+    this.classKind = classKind;
   }
 
-  public void read(ProgramResource resource, ClassKind classKind) throws ResourceException {
-    read(resource.getOrigin(), classKind, resource.getBytes());
+  public void read(ProgramResource resource) throws ResourceException {
+    read(resource.getOrigin(), resource.getBytes());
   }
 
-  public void read(Origin origin, ClassKind classKind, byte[] bytes) {
-    ExceptionUtils.withOriginAttachmentHandler(
-        origin, () -> internalRead(origin, classKind, bytes));
+  public void read(Origin origin, byte[] bytes) {
+    ExceptionUtils.withOriginAttachmentHandler(origin, () -> internalRead(origin, bytes));
   }
 
-  public void internalRead(Origin origin, ClassKind classKind, byte[] bytes) {
+  public void internalRead(Origin origin, byte[] bytes) {
     if (bytes.length < CLASSFILE_HEADER.length) {
       throw new CompilationError("Invalid empty classfile", origin);
     }
@@ -118,7 +117,7 @@
       }
     }
     reader.accept(
-        new CreateDexClassVisitor(origin, classKind, reader.b, application, classConsumer),
+        new CreateDexClassVisitor<>(origin, classKind, reader.b, application, classConsumer),
         parsingOptions);
 
     // Read marker.
@@ -188,12 +187,12 @@
     return new DexEncodedAnnotation(application.getTypeFromDescriptor(desc), elements);
   }
 
-  private static class CreateDexClassVisitor extends ClassVisitor {
+  private static class CreateDexClassVisitor<T extends DexClass> extends ClassVisitor {
 
     private final Origin origin;
-    private final ClassKind classKind;
+    private final ClassKind<T> classKind;
     private final JarApplicationReader application;
-    private final Consumer<DexClass> classConsumer;
+    private final Consumer<T> classConsumer;
     private final ReparseContext context = new ReparseContext();
 
     // DexClass data.
@@ -221,10 +220,10 @@
 
     public CreateDexClassVisitor(
         Origin origin,
-        ClassKind classKind,
+        ClassKind<T> classKind,
         byte[] classCache,
         JarApplicationReader application,
-        Consumer<DexClass> classConsumer) {
+        Consumer<T> classConsumer) {
       super(ASM_VERSION);
       this.origin = origin;
       this.classKind = classKind;
@@ -350,7 +349,7 @@
                 accessFlags,
                 name,
                 version,
-                "must extend class java.lang.Object. Found: " + Objects.toString(superName)),
+                "must extend class java.lang.Object. Found: " + superName),
             origin);
       }
       checkName(name);
@@ -420,7 +419,7 @@
             type, defaultAnnotations, application.getFactory()));
       }
       checkReachabilitySensitivity();
-      DexClass clazz =
+      T clazz =
           classKind.create(
               type,
               Kind.CF,
@@ -483,7 +482,7 @@
       classConsumer.accept(clazz);
     }
 
-    private ChecksumSupplier getChecksumSupplier(ClassKind classKind) {
+    private ChecksumSupplier getChecksumSupplier(ClassKind<T> classKind) {
       if (application.options.encodeChecksums && classKind == ClassKind.PROGRAM) {
         CRC32 crc = new CRC32();
         crc.update(this.context.classCache, 0, this.context.classCache.length);
@@ -567,7 +566,7 @@
 
   private static class CreateFieldVisitor extends FieldVisitor {
 
-    private final CreateDexClassVisitor parent;
+    private final CreateDexClassVisitor<?> parent;
     private final int access;
     private final String name;
     private final String desc;
@@ -575,8 +574,13 @@
     private FieldTypeSignature fieldSignature;
     private List<DexAnnotation> annotations = null;
 
-    public CreateFieldVisitor(CreateDexClassVisitor parent,
-        int access, String name, String desc, String signature, Object value) {
+    public CreateFieldVisitor(
+        CreateDexClassVisitor<?> parent,
+        int access,
+        String name,
+        String desc,
+        String signature,
+        Object value) {
       super(ASM_VERSION);
       this.parent = parent;
       this.access = access;
@@ -670,9 +674,6 @@
       throw new Unreachable("Unexpected static-value type " + type);
     }
 
-    private void addAnnotation(DexAnnotation annotation) {
-      getAnnotations().add(annotation);
-    }
     private List<DexAnnotation> getAnnotations() {
       if (annotations == null) {
         annotations = new ArrayList<>();
@@ -684,7 +685,7 @@
   private static class CreateMethodVisitor extends MethodVisitor {
 
     private final String name;
-    final CreateDexClassVisitor parent;
+    final CreateDexClassVisitor<?> parent;
     private final int parameterCount;
     private List<DexAnnotation> annotations = null;
     private DexValue defaultAnnotation = null;
@@ -698,8 +699,13 @@
     final boolean deprecated;
     Code code = null;
 
-    public CreateMethodVisitor(int access, String name, String desc, String signature,
-        String[] exceptions, CreateDexClassVisitor parent) {
+    public CreateMethodVisitor(
+        int access,
+        String name,
+        String desc,
+        String signature,
+        String[] exceptions,
+        CreateDexClassVisitor<?> parent) {
       super(ASM_VERSION);
       this.name = name;
       this.parent = parent;
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
index 61e54e9..7d6eac0 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
@@ -151,6 +151,10 @@
     set(Constants.ACC_VARARGS);
   }
 
+  public void unsetVarargs() {
+    unset(Constants.ACC_VARARGS);
+  }
+
   public boolean isNative() {
     return isSet(Constants.ACC_NATIVE);
   }
@@ -179,6 +183,10 @@
     set(Constants.ACC_STRICT);
   }
 
+  public void unsetStrict() {
+    unset(Constants.ACC_STRICT);
+  }
+
   public boolean isConstructor() {
     return isSet(Constants.ACC_CONSTRUCTOR);
   }
@@ -207,7 +215,7 @@
     set(Constants.ACC_DECLARED_SYNCHRONIZED);
   }
 
-  private void unsetDeclaredSynchronized() {
+  public void unsetDeclaredSynchronized() {
     unset(Constants.ACC_DECLARED_SYNCHRONIZED);
   }
 
@@ -222,6 +230,24 @@
       return this;
     }
 
+    public Builder setStrict(boolean value) {
+      if (value) {
+        flags.setStrict();
+      } else {
+        flags.unsetStrict();
+      }
+      return this;
+    }
+
+    public Builder setSynchronized(boolean value) {
+      if (value) {
+        flags.setSynchronized();
+      } else {
+        flags.unsetSynchronized();
+      }
+      return this;
+    }
+
     @Override
     public Builder self() {
       return this;
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
index f315247..196bc31 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
@@ -53,6 +53,7 @@
       GraphLens graphLens,
       NamingLens namingLens,
       InitClassLens initClassLens,
+      LensCodeRewriterUtils lensCodeRewriter,
       Collection<DexProgramClass> classes,
       Collection<DexProto> protos,
       Collection<DexType> types,
@@ -76,7 +77,7 @@
     this.graphLens = graphLens;
     this.namingLens = namingLens;
     this.initClassLens = initClassLens;
-    this.lensCodeRewriter = new LensCodeRewriterUtils(appView);
+    this.lensCodeRewriter = lensCodeRewriter;
     timing.begin("Sort strings");
     this.strings = createSortedMap(strings, DexString::compareTo, this::setFirstJumboString);
     CompareToVisitor visitor =
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 09533b0..edb5e46 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -80,11 +80,11 @@
   public abstract boolean isVirtualTarget();
 
   /** Lookup the single target of an invoke-special on this resolution result if possible. */
-  public abstract DexEncodedMethod lookupInvokeSpecialTarget(
+  public abstract DexClassAndMethod lookupInvokeSpecialTarget(
       DexProgramClass context, AppInfoWithClassHierarchy appInfo);
 
   /** Lookup the single target of an invoke-super on this resolution result if possible. */
-  public abstract DexEncodedMethod lookupInvokeSuperTarget(
+  public abstract DexClassAndMethod lookupInvokeSuperTarget(
       DexProgramClass context, AppInfoWithClassHierarchy appInfo);
 
   /** Lookup the single target of an invoke-direct on this resolution result if possible. */
@@ -222,7 +222,7 @@
      * and comments below for deviations due to diverging behavior on actual JVMs.
      */
     @Override
-    public DexEncodedMethod lookupInvokeSpecialTarget(
+    public DexClassAndMethod lookupInvokeSpecialTarget(
         DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
       // If the resolution is non-accessible then no target exists.
       if (isAccessibleFrom(context, appInfo).isPossiblyTrue()) {
@@ -248,7 +248,7 @@
      * @return The actual target for the invoke-super or {@code null} if no valid target is found.
      */
     @Override
-    public DexEncodedMethod lookupInvokeSuperTarget(
+    public DexClassAndMethod lookupInvokeSuperTarget(
         DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
       if (resolvedMethod.isInstanceInitializer()
           || (initialResolutionHolder != context
@@ -305,7 +305,7 @@
       return null;
     }
 
-    private DexEncodedMethod internalInvokeSpecialOrSuper(
+    private DexClassAndMethod internalInvokeSpecialOrSuper(
         DexProgramClass context,
         AppInfoWithClassHierarchy appInfo,
         BiPredicate<DexClass, DexClass> isSuperclass) {
@@ -342,10 +342,10 @@
       }
       // 1-3. Search the initial class and its supers in order for a matching instance method.
       DexMethod method = getResolvedMethod().method;
-      DexEncodedMethod target = null;
+      DexClassAndMethod target = null;
       DexClass current = initialType;
       while (current != null) {
-        target = current.lookupMethod(method);
+        target = current.lookupClassMethod(method);
         if (target != null) {
           break;
         }
@@ -353,29 +353,26 @@
       }
       // 4. Otherwise, it is the single maximally specific method:
       if (target == null) {
-        DexClassAndMethod result = appInfo.lookupMaximallySpecificMethod(initialType, method);
-        if (result != null) {
-          target = result.getDefinition();
-        }
+        target = appInfo.lookupMaximallySpecificMethod(initialType, method);
       }
       if (target == null) {
         return null;
       }
       // Linking exceptions:
       // A non-instance method throws IncompatibleClassChangeError.
-      if (target.isStatic()) {
+      if (target.getAccessFlags().isStatic()) {
         return null;
       }
       // An instance initializer that is not to the symbolic reference throws NoSuchMethodError.
       // It appears as if this check is also in place for non-initializer methods too.
       // See NestInvokeSpecialMethodAccessWithIntermediateTest.
-      if ((target.isInstanceInitializer() || target.isPrivateMethod())
+      if ((target.getDefinition().isInstanceInitializer() || target.getAccessFlags().isPrivate())
           && target.getHolderType() != symbolicReference.type) {
         return null;
       }
       // Runtime exceptions:
       // An abstract method throws AbstractMethodError.
-      if (target.isAbstract()) {
+      if (target.getAccessFlags().isAbstract()) {
         return null;
       }
       return target;
@@ -696,13 +693,13 @@
   abstract static class EmptyResult extends ResolutionResult {
 
     @Override
-    public final DexEncodedMethod lookupInvokeSpecialTarget(
+    public final DexClassAndMethod lookupInvokeSpecialTarget(
         DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
       return null;
     }
 
     @Override
-    public DexEncodedMethod lookupInvokeSuperTarget(
+    public DexClassAndMethod lookupInvokeSuperTarget(
         DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
index 24a1b84..e8130ed 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -116,16 +116,29 @@
   }
 
   private MethodAccessFlags getAccessFlags() {
-    // TODO(b/164998929): ensure this behaviour is correct, should probably calculate upper bound
-    MethodAccessFlags flags = methods.iterator().next().getAccessFlags().copy();
-    if (flags.isAbstract()
-        && Iterables.any(methods, method -> !method.getAccessFlags().isAbstract())) {
-      flags.unsetAbstract();
+    Iterable<MethodAccessFlags> allFlags =
+        Iterables.transform(methods, ProgramMethod::getAccessFlags);
+    MethodAccessFlags result = allFlags.iterator().next().copy();
+    assert Iterables.all(allFlags, flags -> !flags.isNative());
+    assert !result.isStrict() || Iterables.all(allFlags, MethodAccessFlags::isStrict);
+    assert !result.isSynchronized() || Iterables.all(allFlags, MethodAccessFlags::isSynchronized);
+    if (result.isAbstract() && Iterables.any(allFlags, flags -> !flags.isAbstract())) {
+      result.unsetAbstract();
     }
-    if (flags.isFinal() && Iterables.any(methods, method -> !method.getAccessFlags().isFinal())) {
-      flags.unsetFinal();
+    if (result.isBridge() && Iterables.any(allFlags, flags -> !flags.isBridge())) {
+      result.unsetBridge();
     }
-    return flags;
+    if (result.isFinal() && Iterables.any(allFlags, flags -> !flags.isFinal())) {
+      result.unsetFinal();
+    }
+    if (result.isSynthetic() && Iterables.any(allFlags, flags -> !flags.isSynthetic())) {
+      result.unsetSynthetic();
+    }
+    if (result.isVarargs() && Iterables.any(allFlags, flags -> !flags.isVarargs())) {
+      result.unsetVarargs();
+    }
+    result.unsetDeclaredSynchronized();
+    return result;
   }
 
   private DexMethod getNewMethodReference() {
@@ -181,19 +194,24 @@
       }
     }
 
+    DexEncodedMethod newMethod;
     if (representative.getHolder() == group.getTarget()) {
-      classMethodsBuilder.addVirtualMethod(representative.getDefinition());
+      newMethod = representative.getDefinition();
     } else {
       // If the method is not in the target type, move it.
       OptionalBool isLibraryMethodOverride =
           representative.getDefinition().isLibraryMethodOverride();
-      classMethodsBuilder.addVirtualMethod(
+      newMethod =
           representative
               .getDefinition()
               .toTypeSubstitutedMethod(
                   newMethodReference,
-                  builder -> builder.setIsLibraryMethodOverrideIfKnown(isLibraryMethodOverride)));
+                  builder -> builder.setIsLibraryMethodOverrideIfKnown(isLibraryMethodOverride));
     }
+
+    newMethod.getAccessFlags().unsetFinal();
+
+    classMethodsBuilder.addVirtualMethod(newMethod);
   }
 
   public void merge(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
index 5165bd9..431fdb9 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethodSignature;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.horizontalclassmerging.MergeGroup;
 import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
 import com.android.tools.r8.utils.OptionalBool;
@@ -26,17 +27,24 @@
 
   static class MethodCharacteristics {
 
+    private final MethodAccessFlags accessFlags;
     private final OptionalBool isLibraryMethodOverride;
-    private final int visibilityOrdinal;
 
     private MethodCharacteristics(DexEncodedMethod method) {
+      this.accessFlags =
+          MethodAccessFlags.builder()
+              .setPrivate(method.getAccessFlags().isPrivate())
+              .setProtected(method.getAccessFlags().isProtected())
+              .setPublic(method.getAccessFlags().isPublic())
+              .setStrict(method.getAccessFlags().isStrict())
+              .setSynchronized(method.getAccessFlags().isSynchronized())
+              .build();
       this.isLibraryMethodOverride = method.isLibraryMethodOverride();
-      this.visibilityOrdinal = method.getAccessFlags().getVisibilityOrdinal();
     }
 
     @Override
     public int hashCode() {
-      return (visibilityOrdinal << 2) | isLibraryMethodOverride.ordinal();
+      return (accessFlags.hashCode() << 2) | isLibraryMethodOverride.ordinal();
     }
 
     @Override
@@ -49,7 +57,7 @@
       }
       MethodCharacteristics characteristics = (MethodCharacteristics) obj;
       return isLibraryMethodOverride == characteristics.isLibraryMethodOverride
-          && visibilityOrdinal == characteristics.visibilityOrdinal;
+          && accessFlags.equals(characteristics.accessFlags);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
index 095333f..a2c8d57 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
@@ -274,6 +274,15 @@
     return false;
   }
 
+  public final boolean isClassType(DexType type) {
+    assert type.isClassType();
+    return isClassType() && asClassType().getClassType() == type;
+  }
+
+  public final boolean isStringType(DexItemFactory dexItemFactory) {
+    return isClassType(dexItemFactory.stringType);
+  }
+
   public ClassTypeElement asClassType() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index 706d470..62cb5a2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -224,6 +224,12 @@
     return targetFromCondition(1);
   }
 
+  public BasicBlock targetFromNullObject() {
+    assert isZeroTest();
+    assert inValues.get(0).outType().isObject();
+    return targetFromCondition(0);
+  }
+
   public BasicBlock targetFromCondition(int cond) {
     assert Integer.signum(cond) == cond;
     switch (type) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 7fc0d77..e8ba6bd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -129,6 +129,10 @@
     return outValue != null;
   }
 
+  public boolean hasUnusedOutValue() {
+    return !hasOutValue() || !outValue().hasAnyUsers();
+  }
+
   public Value outValue() {
     return outValue;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
index 8ce0b70..0a0461d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
@@ -3,13 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
-import static com.android.tools.r8.graph.DexEncodedMethod.asDexClassAndMethodOrNull;
 
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokeSuperRange;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndMethod;
-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.graph.ProgramMethod;
@@ -114,8 +112,7 @@
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       AppInfoWithLiveness appInfo = appViewWithLiveness.appInfo();
       if (appInfo.isSubtype(context.getHolderType(), getInvokedMethod().holder)) {
-        DexEncodedMethod result = appInfo.lookupSuperTarget(getInvokedMethod(), context);
-        return asDexClassAndMethodOrNull(result, appView);
+        return appInfo.lookupSuperTarget(getInvokedMethod(), context);
       }
     }
     return null;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
index ae5e3f9..4bc8714 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
@@ -39,16 +39,26 @@
 
   private final Map<DexProto, DexProto> protoFixupCache = new ConcurrentHashMap<>();
 
+  // Map from original call sites to rewritten call sites to lookup call-sites when writing.
+  // TODO(b/176885013): This is redundant if we canonicalize call sites.
+  private final Map<DexCallSite, DexCallSite> rewrittenCallSiteCache;
+
   public LensCodeRewriterUtils(AppView<?> appView) {
+    this(appView, false);
+  }
+
+  public LensCodeRewriterUtils(AppView<?> appView, boolean enableCallSiteCaching) {
     this.appView = appView;
     this.definitions = appView;
     this.graphLens = null;
+    this.rewrittenCallSiteCache = enableCallSiteCaching ? new ConcurrentHashMap<>() : null;
   }
 
   public LensCodeRewriterUtils(DexDefinitionSupplier definitions, GraphLens graphLens) {
     this.appView = null;
     this.definitions = definitions;
     this.graphLens = graphLens;
+    this.rewrittenCallSiteCache = null;
   }
 
   private GraphLens graphLens() {
@@ -56,6 +66,14 @@
   }
 
   public DexCallSite rewriteCallSite(DexCallSite callSite, ProgramMethod context) {
+    if (rewrittenCallSiteCache == null) {
+      return rewriteCallSiteInternal(callSite, context);
+    }
+    return rewrittenCallSiteCache.computeIfAbsent(
+        callSite, ignored -> rewriteCallSiteInternal(callSite, context));
+  }
+
+  private DexCallSite rewriteCallSiteInternal(DexCallSite callSite, ProgramMethod context) {
     DexItemFactory dexItemFactory = definitions.dexItemFactory();
     DexProto newMethodProto = rewriteProto(callSite.methodProto);
     DexMethodHandle newBootstrapMethod =
@@ -96,11 +114,15 @@
         // MethodHandles that are not arguments to a lambda metafactory will not be desugared
         // away. Therefore they could flow to a MethodHandle.invokeExact call which means that
         // we cannot member rebind. We therefore keep the receiver and also pin the receiver
-        // with a keep rule (see Enqueuer.registerMethodHandle).
+        // with a keep rule (see Enqueuer.traceMethodHandle).
+        // Note that the member can be repackaged or minified.
         actualTarget =
             definitions
                 .dexItemFactory()
-                .createMethod(invokedMethod.holder, rewrittenTarget.proto, rewrittenTarget.name);
+                .createMethod(
+                    graphLens().lookupType(invokedMethod.holder),
+                    rewrittenTarget.proto,
+                    rewrittenTarget.name);
         newType = oldType;
         if (oldType.isInvokeDirect()) {
           // For an invoke direct, the rewritten target must have the same holder as the original.
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 d6adc3c..40b18c9 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
@@ -1312,15 +1312,20 @@
         // TODO(b/174453232): Remove this after the configuration file format has bee updated
         // with the "rewrite_method" section.
         if (generator.method.getHolderType() == appView.dexItemFactory().objectsType) {
-          // Still backport the new API level 30 methods.
+          // Still backport the new API level 30 methods and Objects.requireNonNull taking
+          // one argument.
           String methodName = generator.method.getName().toString();
-          if (!methodName.equals("requireNonNullElse")
+          if (!methodName.equals("requireNonNull")
+              && !methodName.equals("requireNonNullElse")
               && !methodName.equals("requireNonNullElseGet")
               && !methodName.equals("checkIndex")
               && !methodName.equals("checkFromToIndex")
               && !methodName.equals("checkFromIndexSize")) {
             return;
           }
+          if (methodName.equals("requireNonNull") && generator.method.getArity() != 1) {
+            return;
+          }
         }
       }
       MethodProvider replaced = rewritable.put(generator.method, generator);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
index f5ff0b0..d19fd84 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -182,6 +183,10 @@
     return null;
   }
 
+  public DexMethod retargetMethod(DexClassAndMethod method, AppView<?> appView) {
+    return retargetMethod(method.getDefinition(), appView);
+  }
+
   public Map<DexString, Map<DexType, DexType>> getRetargetCoreLibMember() {
     return retargetCoreLibMember;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
index e04505d..c2b151c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -13,6 +13,7 @@
 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.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -48,6 +49,7 @@
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -179,8 +181,7 @@
               synthesizedMembers
                   .computeIfAbsent(
                       newClass,
-                      ignore ->
-                          new TreeSet<>((x, y) -> x.getReference().compareTo(y.getReference())))
+                      ignore -> new TreeSet<>(Comparator.comparing(DexEncodedMethod::getReference)))
                   .add(
                       new DexEncodedMethod(
                           retargetMethod,
@@ -279,17 +280,14 @@
       // Due to emulated dispatch, we have to rewrite invoke-super differently or we end up in
       // infinite loops. We do direct resolution. This is a very uncommon case.
       if (invoke.isInvokeSuper() && matchesNonFinalHolderRewrite(invoke.getInvokedMethod())) {
-        DexEncodedMethod dexEncodedMethod =
+        DexClassAndMethod superTarget =
             appView
                 .appInfoForDesugaring()
                 .lookupSuperTarget(invoke.getInvokedMethod(), code.context());
         // Final methods can be rewritten as a normal invoke.
-        if (dexEncodedMethod != null && !dexEncodedMethod.isFinal()) {
+        if (superTarget != null && !superTarget.getAccessFlags().isFinal()) {
           DexMethod retargetMethod =
-              appView
-                  .options()
-                  .desugaredLibraryConfiguration
-                  .retargetMethod(dexEncodedMethod, appView);
+              appView.options().desugaredLibraryConfiguration.retargetMethod(superTarget, appView);
           if (retargetMethod != null) {
             iterator.replaceCurrentInstruction(
                 new InvokeStatic(retargetMethod, invoke.outValue(), invoke.arguments()));
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 0159139..eddbf6d 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
@@ -99,6 +99,7 @@
 //   }
 public class DesugaredLibraryWrapperSynthesizer {
 
+  public static final String WRAPPER_PACKAGE = "wrappers/";
   public static final String WRAPPER_PREFIX = "$r8$wrapper$";
   public static final String TYPE_WRAPPER_SUFFIX = "$-WRP";
   public static final String VIVIFIED_TYPE_WRAPPER_SUFFIX = "$-V-WRP";
@@ -125,6 +126,7 @@
                 .options()
                 .desugaredLibraryConfiguration
                 .getSynthesizedLibraryClassesPackagePrefix()
+            + WRAPPER_PACKAGE
             + WRAPPER_PREFIX;
     dexWrapperPrefixDexString = factory.createString(dexWrapperPrefixString);
     this.converter = converter;
@@ -183,7 +185,7 @@
   }
 
   private DexClass generateTypeWrapper(
-      ClassKind classKind, DexClass dexClass, DexType typeWrapperType) {
+      ClassKind<?> classKind, DexClass dexClass, DexType typeWrapperType) {
     DexType type = dexClass.type;
     DexEncodedField wrapperField = synthesizeWrappedValueEncodedField(typeWrapperType, type);
     return synthesizeWrapper(
@@ -196,7 +198,7 @@
   }
 
   private DexClass generateVivifiedTypeWrapper(
-      ClassKind classKind, DexClass dexClass, DexType vivifiedTypeWrapperType) {
+      ClassKind<?> classKind, DexClass dexClass, DexType vivifiedTypeWrapperType) {
     DexType type = dexClass.type;
     DexEncodedField wrapperField =
         synthesizeWrappedValueEncodedField(vivifiedTypeWrapperType, vivifiedTypeFor(type));
@@ -210,7 +212,7 @@
   }
 
   private DexClass synthesizeWrapper(
-      ClassKind classKind,
+      ClassKind<?> classKind,
       DexType wrappingType,
       DexClass clazz,
       DexEncodedMethod[] virtualMethods,
@@ -398,7 +400,7 @@
           for (DexEncodedMethod alreadyImplementedMethod : implementedMethods) {
             if (alreadyImplementedMethod.method.match(virtualMethod.method)) {
               alreadyAdded = true;
-              continue;
+              break;
             }
           }
           if (!alreadyAdded) {
@@ -503,7 +505,7 @@
   }
 
   private void generateWrappers(
-      ClassKind classKind,
+      ClassKind<?> classKind,
       Set<DexType> synthesized,
       BiConsumer<DexType, DexClass> generatedCallback) {
     while (synthesized.size() != typeWrappers.size() + vivifiedTypeWrappers.size()) {
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 bff68b1..47793a8 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
@@ -377,14 +377,14 @@
             if (dexType == null) {
               if (clazz.isInterface()
                   && appView.rewritePrefix.hasRewrittenType(clazz.type, appView)) {
-                DexEncodedMethod target =
+                DexClassAndMethod target =
                     appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, code.context());
-                if (target != null && target.isDefaultMethod()) {
-                  DexClass holder = appView.definitionFor(target.getHolderType());
+                if (target != null && target.getDefinition().isDefaultMethod()) {
+                  DexClass holder = target.getHolder();
                   if (holder.isLibraryClass() && holder.isInterface()) {
                     instructions.replaceCurrentInstruction(
                         new InvokeStatic(
-                            defaultAsMethodOfCompanionClass(target.method, factory),
+                            defaultAsMethodOfCompanionClass(target.getReference(), factory),
                             invokeSuper.outValue(),
                             invokeSuper.arguments()));
                   }
@@ -395,37 +395,33 @@
               // since it's in the emulated interface. We need to force resolution. If it resolves
               // to a library method, then it needs to be rewritten.
               // If it resolves to a program overrides, the invoke-super can remain.
-              DexEncodedMethod dexEncodedMethod =
+              DexClassAndMethod superTarget =
                   appView
                       .appInfoForDesugaring()
                       .lookupSuperTarget(invokeSuper.getInvokedMethod(), code.context());
-              if (dexEncodedMethod != null) {
-                DexClass dexClass = appView.definitionFor(dexEncodedMethod.getHolderType());
-                if (dexClass != null && dexClass.isLibraryClass()) {
-                  // Rewriting is required because the super invoke resolves into a missing
-                  // method (method is on desugared library). Find out if it needs to be
-                  // retarget or if it just calls a companion class method and rewrite.
-                  DexMethod retargetMethod =
-                      options.desugaredLibraryConfiguration.retargetMethod(
-                          dexEncodedMethod, appView);
-                  if (retargetMethod == null) {
-                    DexMethod originalCompanionMethod =
-                        instanceAsMethodOfCompanionClass(
-                            dexEncodedMethod.method, DEFAULT_METHOD_PREFIX, factory);
-                    DexMethod companionMethod =
-                        factory.createMethod(
-                            getCompanionClassType(dexType),
-                            factory.protoWithDifferentFirstParameter(
-                                originalCompanionMethod.proto, dexType),
-                            originalCompanionMethod.name);
-                    instructions.replaceCurrentInstruction(
-                        new InvokeStatic(
-                            companionMethod, invokeSuper.outValue(), invokeSuper.arguments()));
-                  } else {
-                    instructions.replaceCurrentInstruction(
-                        new InvokeStatic(
-                            retargetMethod, invokeSuper.outValue(), invokeSuper.arguments()));
-                  }
+              if (superTarget != null && superTarget.isLibraryMethod()) {
+                // Rewriting is required because the super invoke resolves into a missing
+                // method (method is on desugared library). Find out if it needs to be
+                // retarget or if it just calls a companion class method and rewrite.
+                DexMethod retargetMethod =
+                    options.desugaredLibraryConfiguration.retargetMethod(superTarget, appView);
+                if (retargetMethod == null) {
+                  DexMethod originalCompanionMethod =
+                      instanceAsMethodOfCompanionClass(
+                          superTarget.getReference(), DEFAULT_METHOD_PREFIX, factory);
+                  DexMethod companionMethod =
+                      factory.createMethod(
+                          getCompanionClassType(dexType),
+                          factory.protoWithDifferentFirstParameter(
+                              originalCompanionMethod.proto, dexType),
+                          originalCompanionMethod.name);
+                  instructions.replaceCurrentInstruction(
+                      new InvokeStatic(
+                          companionMethod, invokeSuper.outValue(), invokeSuper.arguments()));
+                } else {
+                  instructions.replaceCurrentInstruction(
+                      new InvokeStatic(
+                          retargetMethod, invokeSuper.outValue(), invokeSuper.arguments()));
                 }
               }
             }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
index 6b6e4b0..eb42231 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
@@ -172,6 +172,8 @@
   protected abstract boolean shouldProcessClassInNest(DexClass clazz, List<DexType> nest);
 
   private DexProgramClass createNestAccessConstructor() {
+    // TODO(b/176900254): The class generated should be in a package that has lower change of
+    //   collisions (and perhaps be unique by some hash from the nest).
     return new DexProgramClass(
         appView.dexItemFactory().nestConstructorType,
         null,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java b/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java
index 8ad22c7..b03cab4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.Map;
 import java.util.Set;
@@ -47,6 +48,8 @@
 
   public abstract boolean isRewriting();
 
+  public abstract boolean shouldRewriteTypeName(String typeName);
+
   public abstract void forAllRewrittenTypes(Consumer<DexType> consumer);
 
   public static class DesugarPrefixRewritingMapper extends PrefixRewritingMapper {
@@ -54,6 +57,7 @@
     private final Set<DexType> notRewritten = Sets.newConcurrentHashSet();
     private final Map<DexType, DexType> rewritten = new ConcurrentHashMap<>();
     private final Map<DexString, DexString> initialPrefixes;
+    private final Set<String> initialPrefixStrings;
     private final DexItemFactory factory;
     private final boolean l8Compilation;
 
@@ -62,10 +66,13 @@
       this.factory = itemFactory;
       this.l8Compilation = libraryCompilation;
       ImmutableMap.Builder<DexString, DexString> builder = ImmutableMap.builder();
+      ImmutableSet.Builder<String> prefixStringBuilder = ImmutableSet.builder();
       for (String key : prefixes.keySet()) {
+        prefixStringBuilder.add(key);
         builder.put(toDescriptorPrefix(key), toDescriptorPrefix(prefixes.get(key)));
       }
       this.initialPrefixes = builder.build();
+      this.initialPrefixStrings = prefixStringBuilder.build();
       validatePrefixes(prefixes);
     }
 
@@ -184,6 +191,17 @@
     public boolean isRewriting() {
       return true;
     }
+
+    @Override
+    public boolean shouldRewriteTypeName(String typeName) {
+      // TODO(b/154800164): We could use tries instead of looking-up everywhere.
+      for (DexString prefix : initialPrefixes.keySet()) {
+        if (typeName.startsWith(prefix.toString())) {
+          return true;
+        }
+      }
+      return false;
+    }
   }
 
   public static class EmptyPrefixRewritingMapper extends PrefixRewritingMapper {
@@ -202,6 +220,11 @@
     }
 
     @Override
+    public boolean shouldRewriteTypeName(String typeName) {
+      return false;
+    }
+
+    @Override
     public void forAllRewrittenTypes(Consumer<DexType> consumer) {}
   }
 }
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 d765d76..89d9eb7 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
@@ -2460,13 +2460,35 @@
     assert code.isConsistentSSA();
   }
 
+  static class ControlFlowSimplificationResult {
+    private boolean anyAffectedValues;
+    private boolean anySimplifications;
+
+    private ControlFlowSimplificationResult(boolean anyAffectedValues, boolean anySimplifications) {
+      assert !anyAffectedValues || anySimplifications;
+      this.anyAffectedValues = anyAffectedValues;
+      this.anySimplifications = anySimplifications;
+    }
+
+    public boolean anyAffectedValues() {
+      return anyAffectedValues;
+    }
+
+    public boolean anySimplifications() {
+      return anySimplifications;
+    }
+  }
+
   public boolean simplifyControlFlow(IRCode code) {
     boolean anyAffectedValues = rewriteSwitch(code);
-    anyAffectedValues |= simplifyIf(code);
+    anyAffectedValues |= simplifyIf(code).anyAffectedValues();
     return anyAffectedValues;
   }
 
-  public boolean simplifyIf(IRCode code) {
+  public ControlFlowSimplificationResult simplifyIf(IRCode code) {
+    BasicBlockBehavioralSubsumption behavioralSubsumption =
+        new BasicBlockBehavioralSubsumption(appView, code);
+    boolean simplified = false;
     for (BasicBlock block : code.blocks) {
       // Skip removed (= unreachable) blocks.
       if (block.getNumber() != 0 && block.getPredecessors().isEmpty()) {
@@ -2474,154 +2496,36 @@
       }
       if (block.exit().isIf()) {
         flipIfBranchesIfNeeded(code, block);
-        rewriteIfWithConstZero(code, block);
+        if (rewriteIfWithConstZero(code, block)) {
+          simplified = true;
+        }
 
         if (simplifyKnownBooleanCondition(code, block)) {
-          continue;
+          simplified = true;
+          if (!block.exit().isIf()) {
+            continue;
+          }
         }
 
         // Simplify if conditions when possible.
         If theIf = block.exit().asIf();
-        Value lhs = theIf.lhs();
-        Value rhs = theIf.isZeroTest() ? null : theIf.rhs();
+        if (theIf.isZeroTest()) {
+          if (simplifyIfZeroTest(code, block, theIf)) {
+            simplified = true;
+            continue;
+          }
+        } else {
+          if (simplifyNonIfZeroTest(code, block, theIf)) {
+            simplified = true;
+            continue;
+          }
+        }
 
-        if (lhs.isConstNumber() && (theIf.isZeroTest() || rhs.isConstNumber())) {
-          // Zero test with a constant of comparison between between two constants.
-          if (theIf.isZeroTest()) {
-            ConstNumber cond = lhs.getConstInstruction().asConstNumber();
-            BasicBlock target = theIf.targetFromCondition(cond);
-            simplifyIfWithKnownCondition(code, block, theIf, target);
-          } else {
-            ConstNumber left = lhs.getConstInstruction().asConstNumber();
-            ConstNumber right = rhs.getConstInstruction().asConstNumber();
-            BasicBlock target = theIf.targetFromCondition(left, right);
-            simplifyIfWithKnownCondition(code, block, theIf, target);
-          }
-        } else if (lhs.hasValueRange() && (theIf.isZeroTest() || rhs.hasValueRange())) {
-          // Zero test with a value range, or comparison between between two values,
-          // each with a value ranges.
-          if (theIf.isZeroTest()) {
-            LongInterval interval = lhs.getValueRange();
-            if (!interval.containsValue(0)) {
-              // Interval doesn't contain zero at all.
-              int sign = Long.signum(interval.getMin());
-              simplifyIfWithKnownCondition(code, block, theIf, sign);
-            } else {
-              // Interval contains zero.
-              switch (theIf.getType()) {
-                case GE:
-                case LT:
-                  // [a, b] >= 0 is always true if a >= 0.
-                  // [a, b] < 0 is always false if a >= 0.
-                  // In both cases a zero condition takes the right branch.
-                  if (interval.getMin() == 0) {
-                    simplifyIfWithKnownCondition(code, block, theIf, 0);
-                  }
-                  break;
-                case LE:
-                case GT:
-                  // [a, b] <= 0 is always true if b <= 0.
-                  // [a, b] > 0 is always false if b <= 0.
-                  if (interval.getMax() == 0) {
-                    simplifyIfWithKnownCondition(code, block, theIf, 0);
-                  }
-                  break;
-                case EQ:
-                case NE:
-                  // Only a single element interval [0, 0] can be dealt with here.
-                  // Such intervals should have been replaced by constants.
-                  assert !interval.isSingleValue();
-                  break;
-              }
-            }
-          } else {
-            LongInterval leftRange = lhs.getValueRange();
-            LongInterval rightRange = rhs.getValueRange();
-            // Two overlapping ranges. Check for single point overlap.
-            if (!leftRange.overlapsWith(rightRange)) {
-              // No overlap.
-              int cond = Long.signum(leftRange.getMin() - rightRange.getMin());
-              simplifyIfWithKnownCondition(code, block, theIf, cond);
-            } else {
-              // The two intervals overlap. We can simplify if they overlap at the end points.
-              switch (theIf.getType()) {
-                case LT:
-                case GE:
-                  // [a, b] < [c, d] is always false when a == d.
-                  // [a, b] >= [c, d] is always true when a == d.
-                  // In both cases 0 condition will choose the right branch.
-                  if (leftRange.getMin() == rightRange.getMax()) {
-                    simplifyIfWithKnownCondition(code, block, theIf, 0);
-                  }
-                  break;
-                case GT:
-                case LE:
-                  // [a, b] > [c, d] is always false when b == c.
-                  // [a, b] <= [c, d] is always true when b == c.
-                  // In both cases 0 condition will choose the right branch.
-                  if (leftRange.getMax() == rightRange.getMin()) {
-                    simplifyIfWithKnownCondition(code, block, theIf, 0);
-                  }
-                  break;
-                case EQ:
-                case NE:
-                  // Since there is overlap EQ and NE cannot be determined.
-                  break;
-              }
-            }
-          }
-        } else if (theIf.getType() == Type.EQ || theIf.getType() == Type.NE) {
-          if (theIf.isZeroTest()) {
-            if (!lhs.isConstNumber()) {
-              TypeElement l = lhs.getType();
-              if (l.isReferenceType() && lhs.isNeverNull()) {
-                simplifyIfWithKnownCondition(code, block, theIf, 1);
-              } else {
-                if (!l.isPrimitiveType() && !l.isNullable()) {
-                  simplifyIfWithKnownCondition(code, block, theIf, 1);
-                }
-              }
-            }
-          } else {
-            ProgramMethod context = code.context();
-            AbstractValue abstractValue = lhs.getAbstractValue(appView, context);
-            if (abstractValue.isSingleConstClassValue()) {
-              AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context);
-              if (otherAbstractValue.isSingleConstClassValue()) {
-                SingleConstClassValue singleConstClassValue =
-                    abstractValue.asSingleConstClassValue();
-                SingleConstClassValue otherSingleConstClassValue =
-                    otherAbstractValue.asSingleConstClassValue();
-                simplifyIfWithKnownCondition(
-                    code,
-                    block,
-                    theIf,
-                    BooleanUtils.intValue(
-                        singleConstClassValue.getType() != otherSingleConstClassValue.getType()));
-              }
-            } else if (abstractValue.isSingleFieldValue()) {
-              AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context);
-              if (otherAbstractValue.isSingleFieldValue()) {
-                SingleFieldValue singleFieldValue = abstractValue.asSingleFieldValue();
-                SingleFieldValue otherSingleFieldValue = otherAbstractValue.asSingleFieldValue();
-                if (singleFieldValue.getField() == otherSingleFieldValue.getField()) {
-                  simplifyIfWithKnownCondition(code, block, theIf, 0);
-                } else {
-                  DexClass holder = appView.definitionForHolder(singleFieldValue.getField());
-                  DexEncodedField field = singleFieldValue.getField().lookupOnClass(holder);
-                  if (field != null && field.isEnum()) {
-                    DexClass otherHolder =
-                        appView.definitionForHolder(otherSingleFieldValue.getField());
-                    DexEncodedField otherField =
-                        otherSingleFieldValue.getField().lookupOnClass(otherHolder);
-                    if (otherField != null && otherField.isEnum()) {
-                      simplifyIfWithKnownCondition(code, block, theIf, 1);
-                    }
-                  }
-                }
-              }
-            }
-          }
+        // Unable to determine which branch will be taken. Check if the true target can safely be
+        // rewritten to the false target.
+        if (behavioralSubsumption.isSubsumedBy(theIf.getTrueTarget(), theIf.fallthroughBlock())) {
+          simplifyIfWithKnownCondition(code, block, theIf, theIf.fallthroughBlock());
+          simplified = true;
         }
       }
     }
@@ -2630,7 +2534,193 @@
       new TypeAnalysis(appView).narrowing(affectedValues);
     }
     assert code.isConsistentSSA();
-    return !affectedValues.isEmpty();
+    return new ControlFlowSimplificationResult(!affectedValues.isEmpty(), simplified);
+  }
+
+  private boolean simplifyIfZeroTest(IRCode code, BasicBlock block, If theIf) {
+    Value lhs = theIf.lhs();
+    Value lhsRoot = lhs.getAliasedValue();
+    if (lhsRoot.isConstNumber()) {
+      ConstNumber cond = lhsRoot.getConstInstruction().asConstNumber();
+      BasicBlock target = theIf.targetFromCondition(cond);
+      simplifyIfWithKnownCondition(code, block, theIf, target);
+      return true;
+    }
+
+    if (theIf.isNullTest()) {
+      assert theIf.getType() == Type.EQ || theIf.getType() == Type.NE;
+
+      if (lhs.isAlwaysNull(appView)) {
+        simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromNullObject());
+        return true;
+      }
+
+      if (lhs.isNeverNull()) {
+        simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromNonNullObject());
+        return true;
+      }
+    }
+
+    if (lhs.hasValueRange()) {
+      LongInterval interval = lhs.getValueRange();
+      if (!interval.containsValue(0)) {
+        // Interval doesn't contain zero at all.
+        int sign = Long.signum(interval.getMin());
+        simplifyIfWithKnownCondition(code, block, theIf, sign);
+        return true;
+      }
+
+      // Interval contains zero.
+      switch (theIf.getType()) {
+        case GE:
+        case LT:
+          // [a, b] >= 0 is always true if a >= 0.
+          // [a, b] < 0 is always false if a >= 0.
+          // In both cases a zero condition takes the right branch.
+          if (interval.getMin() == 0) {
+            simplifyIfWithKnownCondition(code, block, theIf, 0);
+            return true;
+          }
+          break;
+
+        case LE:
+        case GT:
+          // [a, b] <= 0 is always true if b <= 0.
+          // [a, b] > 0 is always false if b <= 0.
+          // In both cases a zero condition takes the right branch.
+          if (interval.getMax() == 0) {
+            simplifyIfWithKnownCondition(code, block, theIf, 0);
+            return true;
+          }
+          break;
+
+        case EQ:
+        case NE:
+          // Only a single element interval [0, 0] can be dealt with here.
+          // Such intervals should have been replaced by constants.
+          assert !interval.isSingleValue();
+          break;
+      }
+    }
+    return false;
+  }
+
+  private boolean simplifyNonIfZeroTest(IRCode code, BasicBlock block, If theIf) {
+    Value lhs = theIf.lhs();
+    Value lhsRoot = lhs.getAliasedValue();
+    Value rhs = theIf.rhs();
+    Value rhsRoot = rhs.getAliasedValue();
+    if (lhsRoot == rhsRoot) {
+      // Comparing the same value.
+      simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(0));
+      return true;
+    }
+
+    if (lhsRoot.isDefinedByInstructionSatisfying(Instruction::isCreatingInstanceOrArray)
+        && rhsRoot.isDefinedByInstructionSatisfying(Instruction::isCreatingInstanceOrArray)) {
+      // Comparing two newly created objects.
+      assert theIf.getType() == Type.EQ || theIf.getType() == Type.NE;
+      simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(1));
+      return true;
+    }
+
+    if (lhsRoot.isConstNumber() && rhsRoot.isConstNumber()) {
+      // Zero test with a constant of comparison between between two constants.
+      ConstNumber left = lhsRoot.getConstInstruction().asConstNumber();
+      ConstNumber right = rhsRoot.getConstInstruction().asConstNumber();
+      BasicBlock target = theIf.targetFromCondition(left, right);
+      simplifyIfWithKnownCondition(code, block, theIf, target);
+      return true;
+    }
+
+    if (lhs.hasValueRange() && rhs.hasValueRange()) {
+      // Zero test with a value range, or comparison between between two values,
+      // each with a value ranges.
+      LongInterval leftRange = lhs.getValueRange();
+      LongInterval rightRange = rhs.getValueRange();
+      // Two overlapping ranges. Check for single point overlap.
+      if (!leftRange.overlapsWith(rightRange)) {
+        // No overlap.
+        int cond = Long.signum(leftRange.getMin() - rightRange.getMin());
+        simplifyIfWithKnownCondition(code, block, theIf, cond);
+        return true;
+      }
+
+      // The two intervals overlap. We can simplify if they overlap at the end points.
+      switch (theIf.getType()) {
+        case LT:
+        case GE:
+          // [a, b] < [c, d] is always false when a == d.
+          // [a, b] >= [c, d] is always true when a == d.
+          // In both cases 0 condition will choose the right branch.
+          if (leftRange.getMin() == rightRange.getMax()) {
+            simplifyIfWithKnownCondition(code, block, theIf, 0);
+            return true;
+          }
+          break;
+        case GT:
+        case LE:
+          // [a, b] > [c, d] is always false when b == c.
+          // [a, b] <= [c, d] is always true when b == c.
+          // In both cases 0 condition will choose the right branch.
+          if (leftRange.getMax() == rightRange.getMin()) {
+            simplifyIfWithKnownCondition(code, block, theIf, 0);
+            return true;
+          }
+          break;
+        case EQ:
+        case NE:
+          // Since there is overlap EQ and NE cannot be determined.
+          break;
+      }
+    }
+
+    if (theIf.getType() == Type.EQ || theIf.getType() == Type.NE) {
+      ProgramMethod context = code.context();
+      AbstractValue abstractValue = lhs.getAbstractValue(appView, context);
+      if (abstractValue.isSingleConstClassValue()) {
+        AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context);
+        if (otherAbstractValue.isSingleConstClassValue()) {
+          SingleConstClassValue singleConstClassValue = abstractValue.asSingleConstClassValue();
+          SingleConstClassValue otherSingleConstClassValue =
+              otherAbstractValue.asSingleConstClassValue();
+          simplifyIfWithKnownCondition(
+              code,
+              block,
+              theIf,
+              BooleanUtils.intValue(
+                  singleConstClassValue.getType() != otherSingleConstClassValue.getType()));
+          return true;
+        }
+        return false;
+      }
+
+      if (abstractValue.isSingleFieldValue()) {
+        AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context);
+        if (otherAbstractValue.isSingleFieldValue()) {
+          SingleFieldValue singleFieldValue = abstractValue.asSingleFieldValue();
+          SingleFieldValue otherSingleFieldValue = otherAbstractValue.asSingleFieldValue();
+          if (singleFieldValue.getField() == otherSingleFieldValue.getField()) {
+            simplifyIfWithKnownCondition(code, block, theIf, 0);
+            return true;
+          }
+
+          DexClass holder = appView.definitionForHolder(singleFieldValue.getField());
+          DexEncodedField field = singleFieldValue.getField().lookupOnClass(holder);
+          if (field != null && field.isEnum()) {
+            DexClass otherHolder = appView.definitionForHolder(otherSingleFieldValue.getField());
+            DexEncodedField otherField =
+                otherSingleFieldValue.getField().lookupOnClass(otherHolder);
+            if (otherField != null && otherField.isEnum()) {
+              simplifyIfWithKnownCondition(code, block, theIf, 1);
+              return true;
+            }
+          }
+        }
+      }
+    }
+
+    return false;
   }
 
   private void simplifyIfWithKnownCondition(
@@ -3069,6 +3159,7 @@
             rewriteIfToGoto(code, block, theIf, trueBlock, falseBlock);
             return true;
           }
+          return deadPhis > 0;
         }
       }
     }
@@ -3154,30 +3245,31 @@
     assert block.exit().asGoto().getTarget() == target;
   }
 
-  private void rewriteIfWithConstZero(IRCode code, BasicBlock block) {
+  private boolean rewriteIfWithConstZero(IRCode code, BasicBlock block) {
     If theIf = block.exit().asIf();
     if (theIf.isZeroTest()) {
-      return;
+      return false;
     }
 
-    List<Value> inValues = theIf.inValues();
-    Value leftValue = inValues.get(0);
-    Value rightValue = inValues.get(1);
+    Value leftValue = theIf.lhs();
+    Value rightValue = theIf.rhs();
     if (leftValue.isConstNumber() || rightValue.isConstNumber()) {
       if (leftValue.isConstNumber()) {
         if (leftValue.getConstInstruction().asConstNumber().isZero()) {
           If ifz = new If(theIf.getType().forSwappedOperands(), rightValue);
           block.replaceLastInstruction(ifz, code);
           assert block.exit() == ifz;
+          return true;
         }
-      } else {
-        if (rightValue.getConstInstruction().asConstNumber().isZero()) {
-          If ifz = new If(theIf.getType(), leftValue);
-          block.replaceLastInstruction(ifz, code);
-          assert block.exit() == ifz;
-        }
+      } else if (rightValue.getConstInstruction().asConstNumber().isZero()) {
+        If ifz = new If(theIf.getType(), leftValue);
+        block.replaceLastInstruction(ifz, code);
+        assert block.exit() == ifz;
+        return true;
       }
     }
+
+    return false;
   }
 
   private boolean flipIfBranchesIfNeeded(IRCode code, BasicBlock block) {
@@ -3709,7 +3801,7 @@
                 && value.definition.asNumberConversion().to == NumericType.DOUBLE) {
               InvokeStatic invokeIsNaN =
                   new InvokeStatic(
-                      dexItemFactory.doubleMethods.isNaN, null, ImmutableList.of(value));
+                      dexItemFactory.doubleMembers.isNaN, null, ImmutableList.of(value));
               invokeIsNaN.setPosition(instruction.getPosition());
 
               // Insert the invoke before the current instruction.
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 26aa681..0ecfc91 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
@@ -53,7 +53,8 @@
         removeDeadInstructions(worklist, code, block);
         removeDeadPhis(worklist, code, block);
       }
-    } while (removeUnneededCatchHandlers(code));
+    } while (codeRewriter.simplifyIf(code).anySimplifications()
+        || removeUnneededCatchHandlers(code));
     assert code.isConsistentSSA();
 
     timing.end();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 4534c9b..79f36ad 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -439,7 +439,10 @@
 
   @Override
   public boolean canInlineInstanceInitializer(
-      IRCode inlinee, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
+      IRCode code,
+      IRCode inlinee,
+      InvokeDirect invoke,
+      WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     // In the Java VM Specification section "4.10.2.4. Instance Initialization Methods and
     // Newly Created Objects" it says:
     //
@@ -451,9 +454,12 @@
     // is expected to adhere to the VM specification.
     DexType callerMethodHolder = method.getHolderType();
     DexType calleeMethodHolder = inlinee.method().getHolderType();
-    // Calling a constructor on the same class from a constructor can always be inlined.
+
+    // Forwarding constructor calls that target a constructor in the same class can always be
+    // inlined.
     if (method.getDefinition().isInstanceInitializer()
-        && callerMethodHolder == calleeMethodHolder) {
+        && callerMethodHolder == calleeMethodHolder
+        && invoke.getReceiver() == code.getThis()) {
       return true;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index f4232a2..674488f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
-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.graph.ProgramMethod;
@@ -310,24 +309,19 @@
   /** This rebinds invoke-super instructions to their most specific target. */
   private DexClassAndMethod rebindSuperInvokeToMostSpecific(
       DexMethod target, ProgramMethod context) {
-    DexEncodedMethod definition = appView.appInfo().lookupSuperTarget(target, context);
-    if (definition == null) {
+    DexClassAndMethod method = appView.appInfo().lookupSuperTarget(target, context);
+    if (method == null) {
       return null;
     }
 
-    DexClass holder = appView.definitionFor(definition.getHolderType());
-    if (holder == null) {
-      assert false;
-      return null;
-    }
-
-    if (holder.isInterface() && holder.getType() != context.getHolder().superType) {
+    if (method.getHolder().isInterface()
+        && method.getHolderType() != context.getHolder().superType) {
       // Not allowed.
       return null;
     }
 
-    DexClassAndMethod method = DexClassAndMethod.create(holder, definition);
-    if (AccessControl.isMemberAccessible(method, holder, context, appView).isPossiblyFalse()) {
+    if (AccessControl.isMemberAccessible(method, method.getHolder(), context, appView)
+        .isPossiblyFalse()) {
       return null;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index eea6b7c..c25755c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
@@ -107,7 +108,10 @@
 
   @Override
   public boolean canInlineInstanceInitializer(
-      IRCode code, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
+      IRCode code,
+      IRCode inlinee,
+      InvokeDirect invoke,
+      WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     return true;
   }
 
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 96c9234..8f510fb 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
@@ -1049,7 +1049,7 @@
           assert !singleTargetMethod.isClassInitializer();
           if (singleTargetMethod.isInstanceInitializer()
               && !strategy.canInlineInstanceInitializer(
-                  inlinee.code, whyAreYouNotInliningReporter)) {
+                  code, inlinee.code, invoke.asInvokeDirect(), whyAreYouNotInliningReporter)) {
             assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
             continue;
           }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
index cda0a6b..1dc076f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
@@ -22,7 +23,10 @@
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
 
   boolean canInlineInstanceInitializer(
-      IRCode code, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
+      IRCode code,
+      IRCode inlinee,
+      InvokeDirect invoke,
+      WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
 
   /** Return true if there is still budget for inlining into this method. */
   boolean stillHasBudget(
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 ed477a6..e62b2c1 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
@@ -122,7 +122,7 @@
 
   @Override
   public void methodMayNotHaveSideEffects(DexEncodedMethod method) {
-    // Ignored.
+    method.getMutableOptimizationInfo().markMayNotHaveSideEffects();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
index 25916f9..01c4cf7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
@@ -135,6 +135,13 @@
         continue;
       }
 
+      if (invoke.hasUnusedOutValue()
+          && !singleTarget.getDefinition().isInstanceInitializer()
+          && !invoke.instructionMayHaveSideEffects(appView, code.context())) {
+        instructionIterator.removeOrReplaceByDebugLocalRead();
+        continue;
+      }
+
       LibraryMethodModelCollection.State optimizationState =
           optimizationStates.computeIfAbsent(
               optimizer,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
index 70ace2b..b1890af 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
@@ -4,41 +4,41 @@
 
 package com.android.tools.r8.ir.optimize.library;
 
-import static com.google.common.base.Predicates.alwaysFalse;
-import static com.google.common.base.Predicates.alwaysTrue;
-
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.LibraryMethod;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.BiPredicateUtils;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Predicate;
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
 
 public class LibraryMethodSideEffectModelCollection {
 
-  private final Map<DexMethod, Predicate<InvokeMethod>> finalMethodsWithoutSideEffects;
+  private final Map<DexMethod, BiPredicate<DexMethod, List<Value>>> finalMethodsWithoutSideEffects;
+  private final Set<DexMethod> unconditionalFinalMethodsWithoutSideEffects;
+
   private final Set<DexMethod> nonFinalMethodsWithoutSideEffects;
 
   public LibraryMethodSideEffectModelCollection(DexItemFactory dexItemFactory) {
     finalMethodsWithoutSideEffects = buildFinalMethodsWithoutSideEffects(dexItemFactory);
+    unconditionalFinalMethodsWithoutSideEffects =
+        buildUnconditionalFinalMethodsWithoutSideEffects(dexItemFactory);
     nonFinalMethodsWithoutSideEffects = buildNonFinalMethodsWithoutSideEffects(dexItemFactory);
   }
 
-  private static Map<DexMethod, Predicate<InvokeMethod>> buildFinalMethodsWithoutSideEffects(
-      DexItemFactory dexItemFactory) {
-    ImmutableMap.Builder<DexMethod, Predicate<InvokeMethod>> builder =
-        ImmutableMap.<DexMethod, Predicate<InvokeMethod>>builder()
-            .put(dexItemFactory.enumMembers.constructor, alwaysTrue())
-            .put(dexItemFactory.npeMethods.init, alwaysTrue())
-            .put(dexItemFactory.npeMethods.initWithMessage, alwaysTrue())
-            .put(dexItemFactory.objectMembers.constructor, alwaysTrue())
-            .put(dexItemFactory.objectMembers.getClass, alwaysTrue())
-            .put(dexItemFactory.stringBuilderMethods.toString, alwaysTrue())
-            .put(dexItemFactory.stringMembers.hashCode, alwaysTrue());
-    putAll(builder, dexItemFactory.classMethods.getNames, alwaysTrue());
+  private static Map<DexMethod, BiPredicate<DexMethod, List<Value>>>
+      buildFinalMethodsWithoutSideEffects(DexItemFactory dexItemFactory) {
+    ImmutableMap.Builder<DexMethod, BiPredicate<DexMethod, List<Value>>> builder =
+        ImmutableMap.<DexMethod, BiPredicate<DexMethod, List<Value>>>builder()
+            .put(
+                dexItemFactory.stringMembers.constructor,
+                (method, arguments) -> arguments.get(1).isNeverNull());
     putAll(
         builder,
         dexItemFactory.stringBufferMethods.constructorMethods,
@@ -47,10 +47,34 @@
         builder,
         dexItemFactory.stringBuilderMethods.constructorMethods,
         dexItemFactory.stringBuilderMethods::constructorInvokeIsSideEffectFree);
-    putAll(builder, dexItemFactory.boxedValueOfMethods(), alwaysTrue());
     return builder.build();
   }
 
+  private static Set<DexMethod> buildUnconditionalFinalMethodsWithoutSideEffects(
+      DexItemFactory dexItemFactory) {
+    return ImmutableSet.<DexMethod>builder()
+        .add(dexItemFactory.booleanMembers.toString)
+        .add(dexItemFactory.byteMembers.toString)
+        .add(dexItemFactory.charMembers.toString)
+        .add(dexItemFactory.doubleMembers.toString)
+        .add(dexItemFactory.enumMembers.constructor)
+        .add(dexItemFactory.floatMembers.toString)
+        .add(dexItemFactory.integerMembers.toString)
+        .add(dexItemFactory.longMembers.toString)
+        .add(dexItemFactory.npeMethods.init)
+        .add(dexItemFactory.npeMethods.initWithMessage)
+        .add(dexItemFactory.objectMembers.constructor)
+        .add(dexItemFactory.objectMembers.getClass)
+        .add(dexItemFactory.shortMembers.toString)
+        .add(dexItemFactory.stringBufferMethods.toString)
+        .add(dexItemFactory.stringBuilderMethods.toString)
+        .add(dexItemFactory.stringMembers.hashCode)
+        .add(dexItemFactory.stringMembers.toString)
+        .addAll(dexItemFactory.classMethods.getNames)
+        .addAll(dexItemFactory.boxedValueOfMethods())
+        .build();
+  }
+
   private static Set<DexMethod> buildNonFinalMethodsWithoutSideEffects(
       DexItemFactory dexItemFactory) {
     return ImmutableSet.of(
@@ -59,25 +83,31 @@
         dexItemFactory.objectMembers.toString);
   }
 
-  private static void putAll(
-      ImmutableMap.Builder<DexMethod, Predicate<InvokeMethod>> builder,
-      Iterable<DexMethod> methods,
-      Predicate<InvokeMethod> predicate) {
-    for (DexMethod method : methods) {
-      builder.put(method, predicate);
+  private static <K, V> void putAll(ImmutableMap.Builder<K, V> builder, Iterable<K> keys, V value) {
+    for (K key : keys) {
+      builder.put(key, value);
     }
   }
 
+  public void forEachSideEffectFreeFinalMethod(Consumer<DexMethod> consumer) {
+    unconditionalFinalMethodsWithoutSideEffects.forEach(consumer);
+  }
+
   public boolean isCallToSideEffectFreeFinalMethod(InvokeMethod invoke) {
-    return finalMethodsWithoutSideEffects
-        .getOrDefault(invoke.getInvokedMethod(), alwaysFalse())
-        .test(invoke);
+    return isSideEffectFreeFinalMethod(invoke.getInvokedMethod(), invoke.arguments());
+  }
+
+  public boolean isSideEffectFreeFinalMethod(DexMethod method, List<Value> arguments) {
+    return unconditionalFinalMethodsWithoutSideEffects.contains(method)
+        || finalMethodsWithoutSideEffects
+            .getOrDefault(method, BiPredicateUtils.alwaysFalse())
+            .test(method, arguments);
   }
 
   // This intentionally takes the invoke instruction since the determination of whether a library
   // method has side effects may depend on the arguments.
   public boolean isSideEffectFree(InvokeMethod invoke, LibraryMethod singleTarget) {
-    return isCallToSideEffectFreeFinalMethod(invoke)
+    return isSideEffectFreeFinalMethod(singleTarget.getReference(), invoke.arguments())
         || nonFinalMethodsWithoutSideEffects.contains(singleTarget.getReference());
   }
 }
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
index 9b41f18..ecbb3e4 100644
--- 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
@@ -47,6 +47,7 @@
     modelStaticFinalLibraryFields(finalLibraryFields);
     modelLibraryMethodsReturningNonNull();
     modelLibraryMethodsReturningReceiver();
+    modelLibraryMethodsWithoutSideEffects();
     modelRequireNonNullMethods();
   }
 
@@ -113,6 +114,18 @@
     }
   }
 
+  private void modelLibraryMethodsWithoutSideEffects() {
+    appView
+        .getLibraryMethodSideEffectModelCollection()
+        .forEachSideEffectFreeFinalMethod(
+            method -> {
+              DexEncodedMethod definition = lookupMethod(method);
+              if (definition != null) {
+                feedback.methodMayNotHaveSideEffects(definition);
+              }
+            });
+  }
+
   private void modelRequireNonNullMethods() {
     for (DexMethod requireNonNullMethod : dexItemFactory.objectsMethods.requireNonNullMethods()) {
       DexEncodedMethod definition = lookupMethod(requireNonNullMethod);
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
index 9d0c8c8..a303082 100644
--- 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
@@ -5,14 +5,19 @@
 package com.android.tools.r8.ir.optimize.library;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexItemFactory.ObjectsMethods;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 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 com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Set;
 
 public class ObjectsMethodOptimizer extends StatelessLibraryMethodModelCollection {
@@ -67,11 +72,62 @@
       InvokeMethod invoke,
       Set<Value> affectedValues) {
     Value object = invoke.getFirstArgument();
-    if (object.getType().isDefinitelyNull()) {
+    TypeElement type = object.getType();
+
+    // Optimize Objects.toString(null) into "null".
+    if (type.isDefinitelyNull()) {
       instructionIterator.replaceCurrentInstructionWithConstString(appView, code, "null");
       if (invoke.hasOutValue()) {
         affectedValues.addAll(invoke.outValue().affectedValues());
       }
+      return;
+    }
+
+    // Optimize Objects.toString(nonNullString) into nonNullString.
+    if (type.isDefinitelyNotNull() && type.isStringType(dexItemFactory)) {
+      if (invoke.hasOutValue()) {
+        affectedValues.addAll(invoke.outValue().affectedValues());
+        invoke.outValue().replaceUsers(object);
+      }
+      instructionIterator.removeOrReplaceByDebugLocalRead();
+      return;
+    }
+
+    // Remove Objects.toString() if it is unused and does not have side effects.
+    if (!invoke.hasOutValue() || !invoke.outValue().hasNonDebugUsers()) {
+      // Calling toString() on an array does not call toString() on the array elements.
+      if (type.isArrayType()) {
+        instructionIterator.removeOrReplaceByDebugLocalRead();
+        return;
+      }
+
+      assert type.isClassType();
+
+      // Check if this is a library class with a toString() method that does not have side effects.
+      DexType classType = type.asClassType().getClassType();
+      DexMethod toStringMethodReference =
+          dexItemFactory.objectMembers.toString.withHolder(classType, dexItemFactory);
+      if (appView
+          .getLibraryMethodSideEffectModelCollection()
+          .isSideEffectFreeFinalMethod(toStringMethodReference, invoke.arguments())) {
+        instructionIterator.removeOrReplaceByDebugLocalRead();
+        return;
+      }
+
+      // Check if this is a program class with a toString() method that does not have side effects.
+      AppInfoWithLiveness appInfo = appView.appInfo().withLiveness();
+      if (appInfo != null) {
+        DexClass clazz = appInfo.definitionFor(classType, code.context());
+        if (clazz != null && clazz.isEffectivelyFinal(appView)) {
+          SingleResolutionResult resolutionResult =
+              appInfo.resolveMethodOn(clazz, toStringMethodReference).asSingleResolution();
+          if (resolutionResult != null
+              && !resolutionResult.getResolvedMethod().getOptimizationInfo().mayHaveSideEffects()) {
+            instructionIterator.removeOrReplaceByDebugLocalRead();
+            return;
+          }
+        }
+      }
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
index 9cccb5d..99b1bda 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
@@ -4,7 +4,10 @@
 
 package com.android.tools.r8.ir.optimize.library;
 
+import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
+import static com.android.tools.r8.ir.code.Opcodes.IF;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
 import static com.android.tools.r8.ir.code.Opcodes.NEW_INSTANCE;
 
@@ -29,6 +32,7 @@
 import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
 import com.android.tools.r8.ir.optimize.library.StringBuilderMethodOptimizer.State;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ValueUtils;
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.objects.Reference2BooleanMap;
@@ -73,6 +77,8 @@
       InvokeMethodWithReceiver invokeWithReceiver = invoke.asInvokeMethodWithReceiver();
       if (stringBuilderMethods.isAppendMethod(singleTarget.getReference())) {
         optimizeAppend(code, instructionIterator, invokeWithReceiver, singleTarget, state);
+      } else if (singleTarget.getReference() == dexItemFactory.stringBuilderMethods.toString) {
+        optimizeToString(instructionIterator, invokeWithReceiver);
       }
     }
   }
@@ -130,6 +136,16 @@
     }
   }
 
+  private void optimizeToString(
+      InstructionListIterator instructionIterator, InvokeMethodWithReceiver invoke) {
+    // Optimize StringBuilder.toString() if unused.
+    if (ValueUtils.isNonNullStringBuilder(invoke.getReceiver(), dexItemFactory)) {
+      if (!invoke.hasOutValue() || !invoke.outValue().hasNonDebugUsers()) {
+        instructionIterator.removeOrReplaceByDebugLocalRead();
+      }
+    }
+  }
+
   class State implements LibraryMethodModelCollection.State {
 
     final MethodProcessor methodProcessor;
@@ -185,6 +201,10 @@
 
         Instruction definition = alias.definition;
         switch (definition.opcode()) {
+          case ASSUME:
+            worklist.addIfNotSeen(definition.inValues());
+            break;
+
           case NEW_INSTANCE:
             assert definition.asNewInstance().clazz == dexItemFactory.stringBuilderType;
             break;
@@ -208,6 +228,14 @@
         // Analyze all users.
         for (Instruction user : alias.uniqueUsers()) {
           switch (user.opcode()) {
+            case ASSUME:
+              worklist.addIfNotSeen(user.outValue());
+              break;
+
+            case IF:
+              // StringBuilder null check.
+              break;
+
             case INVOKE_DIRECT:
               {
                 InvokeDirect invoke = user.asInvokeDirect();
@@ -224,6 +252,25 @@
               }
               break;
 
+            case INVOKE_STATIC:
+              {
+                InvokeStatic invoke = user.asInvokeStatic();
+                DexMethod invokedMethod = invoke.getInvokedMethod();
+
+                // Allow calls to Objects.toString(Object) and String.valueOf(Object).
+                if (invokedMethod == dexItemFactory.objectsMethods.toStringWithObject
+                    || invokedMethod == dexItemFactory.stringMembers.valueOf) {
+                  // Only allow unused StringBuilders.
+                  if (invoke.hasOutValue() && invoke.outValue().hasNonDebugUsers()) {
+                    return false;
+                  }
+                  break;
+                }
+
+                // Invoke to unhandled method, give up.
+                return false;
+              }
+
             case INVOKE_VIRTUAL:
               {
                 InvokeVirtual invoke = user.asInvokeVirtual();
@@ -245,7 +292,8 @@
                 }
 
                 // Allow calls to toString().
-                if (invokedMethod == stringBuilderMethods.toString) {
+                if (invokedMethod == dexItemFactory.objectMembers.toString
+                    || invokedMethod == stringBuilderMethods.toString) {
                   // Only allow unused StringBuilders.
                   if (invoke.hasOutValue() && invoke.outValue().hasNonDebugUsers()) {
                     return false;
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 c8d04ae..a8fabe3 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
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -15,7 +16,10 @@
 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.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.ValueUtils;
 import java.util.Set;
 
 public class StringMethodOptimizer extends StatelessLibraryMethodModelCollection {
@@ -40,17 +44,20 @@
       InvokeMethod invoke,
       DexClassAndMethod singleTarget,
       Set<Value> affectedValues) {
-    if (singleTarget.getReference() == dexItemFactory.stringMembers.equals) {
-      optimizeEquals(code, instructionIterator, invoke);
+    DexMethod singleTargetReference = singleTarget.getReference();
+    if (singleTargetReference == dexItemFactory.stringMembers.equals) {
+      optimizeEquals(code, instructionIterator, invoke.asInvokeVirtual());
+    } else if (singleTargetReference == dexItemFactory.stringMembers.valueOf) {
+      optimizeValueOf(instructionIterator, invoke.asInvokeStatic());
     }
   }
 
   private void optimizeEquals(
-      IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke) {
+      IRCode code, InstructionListIterator instructionIterator, InvokeVirtual invoke) {
     if (appView.appInfo().hasLiveness()) {
       ProgramMethod context = code.context();
-      Value first = invoke.arguments().get(0).getAliasedValue();
-      Value second = invoke.arguments().get(1).getAliasedValue();
+      Value first = invoke.getReceiver().getAliasedValue();
+      Value second = invoke.getArgument(1).getAliasedValue();
       if (isPrunedClassNameComparison(first, second, context)
           || isPrunedClassNameComparison(second, first, context)) {
         instructionIterator.replaceCurrentInstructionWithConstInt(code, 0);
@@ -58,6 +65,15 @@
     }
   }
 
+  private void optimizeValueOf(InstructionListIterator instructionIterator, InvokeStatic invoke) {
+    // Optimize String.valueOf(stringBuilder) if unused.
+    if (ValueUtils.isNonNullStringBuilder(invoke.getFirstArgument(), dexItemFactory)) {
+      if (!invoke.hasOutValue() || !invoke.outValue().hasNonDebugUsers()) {
+        instructionIterator.removeOrReplaceByDebugLocalRead();
+      }
+    }
+  }
+
   /**
    * Returns true if {@param classNameValue} is defined by calling {@link Class#getName()} and
    * {@param constStringValue} is a constant string that is identical to the name of a class that
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
index a61662c..3864fb8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -23,7 +23,9 @@
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.DominatorTree;
 import com.android.tools.r8.ir.code.DominatorTree.Assumption;
+import com.android.tools.r8.ir.code.Goto;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeDirect;
@@ -35,6 +37,7 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableSet;
@@ -731,13 +734,34 @@
       if (deadBuilders.isEmpty() && simplifiedBuilders.isEmpty()) {
         return;
       }
-      Set<Value> buildersToRemove = Sets.union(deadBuilders, simplifiedBuilders);
+      Set<Value> affectedValues = Sets.newIdentityHashSet();
+      Set<Value> buildersToRemove = SetUtils.newIdentityHashSet(deadBuilders, simplifiedBuilders);
+      Set<Value> buildersUsedInIf = Sets.newIdentityHashSet();
       // All instructions that refer to dead/simplified builders are dead.
       // Here, we remove toString() calls, append(...) calls, <init>, and new-instance in order.
       InstructionListIterator it = code.instructionListIterator();
+      boolean shouldRemoveUnreachableBlocks = false;
       while (it.hasNext()) {
         Instruction instr = it.next();
-        if (instr.isInvokeMethod()) {
+        if (instr.isIf()) {
+          If theIf = instr.asIf();
+          Value lhs = theIf.lhs().getAliasedValue();
+          if (theIf.isZeroTest()) {
+            if (buildersToRemove.contains(lhs)) {
+              theIf.targetFromNullObject().unlinkSinglePredecessorSiblingsAllowed();
+              it.replaceCurrentInstruction(new Goto());
+              shouldRemoveUnreachableBlocks = true;
+            }
+          } else {
+            Value rhs = theIf.rhs().getAliasedValue();
+            if (buildersToRemove.contains(lhs)) {
+              buildersUsedInIf.add(lhs);
+            }
+            if (buildersToRemove.contains(rhs)) {
+              buildersUsedInIf.add(rhs);
+            }
+          }
+        } else if (instr.isInvokeMethod()) {
           InvokeMethod invoke = instr.asInvokeMethod();
           DexMethod invokedMethod = invoke.getInvokedMethod();
           if (optimizationConfiguration.isToStringMethod(invokedMethod)
@@ -746,6 +770,10 @@
           }
         }
       }
+      if (shouldRemoveUnreachableBlocks) {
+        affectedValues.addAll(code.removeUnreachableBlocks());
+      }
+      buildersToRemove.removeAll(buildersUsedInIf);
       // append(...) and <init> don't have out values, so removing them won't bother each other.
       it = code.instructionListIterator();
       while (it.hasNext()) {
@@ -787,6 +815,9 @@
           it.removeOrReplaceByDebugLocalRead();
         }
       }
+      if (!affectedValues.isEmpty()) {
+        new TypeAnalysis(appView).narrowing(affectedValues);
+      }
       assert code.isConsistentSSA();
     }
   }
@@ -852,7 +883,8 @@
 
     @Override
     public boolean isToStringMethod(DexMethod method) {
-      return method == factory.stringBuilderMethods.toString
+      return method == factory.objectMembers.toString
+          || method == factory.stringBuilderMethods.toString
           || method == factory.stringBufferMethods.toString
           || method == factory.stringMembers.valueOf;
     }
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index b9fc4cb..a775a72 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.graph.DexEncodedField;
 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.DexValue;
 import com.android.tools.r8.graph.DexValue.DexItemBasedValueString;
 import com.android.tools.r8.graph.DexValue.DexValueString;
@@ -25,7 +24,6 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
@@ -104,21 +102,10 @@
   }
 
   private DexString getRenamedStringLiteral(DexString originalLiteral) {
-    String originalString = originalLiteral.toString();
-    Map<String, DexType> renamedYetMatchedTypes =
-        lens.getRenamedItems(
-            DexType.class,
-            type -> type.toSourceString().equals(originalString),
-            DexType::toSourceString);
-    DexType type = renamedYetMatchedTypes.get(originalString);
-    if (type != null) {
-      DexString renamed = lens.lookupDescriptor(type);
-      // Create a new DexString only when the corresponding string literal will be replaced.
-      if (renamed != originalLiteral) {
-        return appView.dexItemFactory().createString(descriptorToJavaType(renamed.toString()));
-      }
-    }
-    return originalLiteral;
+    DexString rewrittenString = lens.lookupDescriptorForJavaTypeName(originalLiteral.toString());
+    return rewrittenString == null
+        ? originalLiteral
+        : appView.dexItemFactory().createString(descriptorToJavaType(rewrittenString.toString()));
   }
 
   private void replaceDexItemBasedConstString(ExecutorService executorService)
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index b7bfcdb..eb4e9ca 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -20,13 +20,10 @@
 import com.android.tools.r8.naming.NamingLens.NonIdentityNamingLens;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableMap;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.function.Function;
-import java.util.function.Predicate;
 
 class MinifiedRenaming extends NonIdentityNamingLens {
 
@@ -39,7 +36,7 @@
       ClassRenaming classRenaming,
       MethodRenaming methodRenaming,
       FieldRenaming fieldRenaming) {
-    super(appView.dexItemFactory());
+    super(appView.dexItemFactory(), classRenaming.classRenaming);
     this.appView = appView;
     this.packageRenaming = classRenaming.packageRenaming;
     renaming.putAll(classRenaming.classRenaming);
@@ -123,15 +120,6 @@
     return renaming.getOrDefault(field, field.name);
   }
 
-  @Override
-  public <T extends DexItem> Map<String, T> getRenamedItems(
-      Class<T> clazz, Predicate<T> predicate, Function<T, String> namer) {
-    return renaming.keySet().stream()
-        .filter(item -> (clazz.isInstance(item) && predicate.test(clazz.cast(item))))
-        .map(clazz::cast)
-        .collect(ImmutableMap.toImmutableMap(namer, i -> i));
-  }
-
   /**
    * Checks that the renaming of the method reference {@param method} is consistent with the
    * renaming of the resolution target of {@param method}.
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index 9147b84..bdd0e70 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -20,13 +19,11 @@
 import com.android.tools.r8.optimize.MemberRebindingAnalysis;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Sets;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Function;
-import java.util.function.Predicate;
 
 /**
  * Implements a translation of the Dex graph from original names to new names produced by the {@link
@@ -45,6 +42,8 @@
 
   public abstract DexString lookupDescriptor(DexType type);
 
+  public abstract DexString lookupDescriptorForJavaTypeName(String typeName);
+
   public DexString lookupClassDescriptor(DexType type) {
     assert type.isClassType();
     return internalLookupClassDescriptor(type);
@@ -147,9 +146,6 @@
     return DescriptorUtils.descriptorToInternalName(lookupDescriptor(type).toString());
   }
 
-  public abstract <T extends DexItem> Map<String, T> getRenamedItems(
-      Class<T> clazz, Predicate<T> predicate, Function<T, String> namer);
-
   /**
    * Checks whether the target will be translated properly by this lens.
    *
@@ -189,9 +185,13 @@
   public abstract static class NonIdentityNamingLens extends NamingLens {
 
     private final DexItemFactory dexItemFactory;
+    private final Map<String, DexString> typeStringMapping;
 
-    protected NonIdentityNamingLens(DexItemFactory dexItemFactory) {
+    protected NonIdentityNamingLens(
+        DexItemFactory dexItemFactory, Map<DexType, DexString> typeMapping) {
       this.dexItemFactory = dexItemFactory;
+      typeStringMapping = new HashMap<>();
+      typeMapping.forEach((k, v) -> typeStringMapping.put(k.toSourceString(), v));
     }
 
     protected DexItemFactory dexItemFactory() {
@@ -211,6 +211,11 @@
       assert type.isClassType();
       return lookupClassDescriptor(type);
     }
+
+    @Override
+    public DexString lookupDescriptorForJavaTypeName(String typeName) {
+      return typeStringMapping.get(typeName);
+    }
   }
 
   private static final class IdentityLens extends NamingLens {
@@ -225,6 +230,11 @@
     }
 
     @Override
+    public DexString lookupDescriptorForJavaTypeName(String typeName) {
+      return null;
+    }
+
+    @Override
     protected DexString internalLookupClassDescriptor(DexType type) {
       return type.descriptor;
     }
@@ -250,12 +260,6 @@
     }
 
     @Override
-    public <T extends DexItem> Map<String, T> getRenamedItems(
-        Class<T> clazz, Predicate<T> predicate, Function<T, String> namer) {
-      return ImmutableMap.of();
-    }
-
-    @Override
     public boolean verifyRenamingConsistentWithResolution(DexMethod item) {
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java b/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java
index e987def..04f235f 100644
--- a/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java
@@ -6,20 +6,13 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.naming.NamingLens.NonIdentityNamingLens;
 import com.android.tools.r8.utils.InternalOptions;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.function.Function;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
+import java.util.IdentityHashMap;
 
 // Naming lens for rewriting type prefixes.
 public class PrefixRewritingNamingLens extends NonIdentityNamingLens {
@@ -41,7 +34,7 @@
   }
 
   public PrefixRewritingNamingLens(NamingLens namingLens, AppView<?> appView) {
-    super(appView.dexItemFactory());
+    super(appView.dexItemFactory(), new IdentityHashMap<>());
     this.appView = appView;
     this.namingLens = namingLens;
     this.options = appView.options();
@@ -103,6 +96,18 @@
   }
 
   @Override
+  public DexString lookupDescriptorForJavaTypeName(String typeName) {
+    if (appView.rewritePrefix.shouldRewriteTypeName(typeName)) {
+      DexType rewrittenType =
+          appView.rewritePrefix.rewrittenType(dexItemFactory().createType(typeName), appView);
+      if (rewrittenType != null) {
+        return rewrittenType.descriptor;
+      }
+    }
+    return namingLens.lookupDescriptorForJavaTypeName(typeName);
+  }
+
+  @Override
   public String lookupPackageName(String packageName) {
     // Used for resource shrinking.
     // Desugared libraries do not have resources.
@@ -122,30 +127,6 @@
   }
 
   @Override
-  public <T extends DexItem> Map<String, T> getRenamedItems(
-      Class<T> clazz, Predicate<T> predicate, Function<T, String> namer) {
-    Map<String, T> renamedItemsPrefixRewritting;
-    if (clazz == DexType.class) {
-      renamedItemsPrefixRewritting = new HashMap<>();
-      appView.rewritePrefix.forAllRewrittenTypes(
-          item -> {
-            T cast = clazz.cast(item);
-            if (predicate.test(cast)) {
-              renamedItemsPrefixRewritting.put(namer.apply(cast), cast);
-            }
-          });
-    } else {
-      renamedItemsPrefixRewritting = Collections.emptyMap();
-    }
-    Map<String, T> renamedItemsMinifier = namingLens.getRenamedItems(clazz, predicate, namer);
-    // The Collector throws an exception for duplicated keys.
-    return Stream.concat(
-            renamedItemsPrefixRewritting.entrySet().stream(),
-            renamedItemsMinifier.entrySet().stream())
-        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
-  }
-
-  @Override
   public boolean verifyRenamingConsistentWithResolution(DexMethod item) {
     return namingLens.verifyRenamingConsistentWithResolution(item);
   }
diff --git a/src/main/java/com/android/tools/r8/relocator/RelocatorCommandLine.java b/src/main/java/com/android/tools/r8/relocator/RelocatorCommandLine.java
index f255aee..dc11daa 100644
--- a/src/main/java/com/android/tools/r8/relocator/RelocatorCommandLine.java
+++ b/src/main/java/com/android/tools/r8/relocator/RelocatorCommandLine.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.Version;
 import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.utils.ExceptionUtils;
+import com.android.tools.r8.utils.StringUtils;
 
 public class RelocatorCommandLine {
 
@@ -21,8 +22,7 @@
    */
   public static void main(String[] args) {
     if (args.length == 0) {
-      System.err.println(USAGE_MESSAGE);
-      System.exit(ExceptionUtils.STATUS_ERROR);
+      throw new RuntimeException(StringUtils.joinLines("Invalid invocation.", USAGE_MESSAGE));
     }
     ExceptionUtils.withMainProgramHandler(() -> run(args));
   }
diff --git a/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java b/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java
index 09ca5bd..3a5902a 100644
--- a/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java
+++ b/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -22,8 +21,6 @@
 import com.google.common.collect.ImmutableMap;
 import java.util.IdentityHashMap;
 import java.util.Map;
-import java.util.function.Function;
-import java.util.function.Predicate;
 
 class SimplePackagesRewritingMapper {
 
@@ -105,7 +102,7 @@
         Map<DexType, DexString> typeMappings,
         Map<String, String> packageMappings,
         DexItemFactory factory) {
-      super(factory);
+      super(factory, typeMappings);
       this.typeMappings = typeMappings;
       this.packageMappings = packageMappings;
     }
@@ -136,15 +133,6 @@
     }
 
     @Override
-    public <T extends DexItem> Map<String, T> getRenamedItems(
-        Class<T> clazz, Predicate<T> predicate, Function<T, String> namer) {
-      return typeMappings.keySet().stream()
-          .filter(item -> (clazz.isInstance(item) && predicate.test(clazz.cast(item))))
-          .map(clazz::cast)
-          .collect(ImmutableMap.toImmutableMap(namer, i -> i));
-    }
-
-    @Override
     public boolean verifyRenamingConsistentWithResolution(DexMethod item) {
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index d202954..0d66a64 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.retrace;
 
 import static com.android.tools.r8.retrace.internal.RetraceUtils.firstNonWhiteSpaceCharacterFromIndex;
-import static com.android.tools.r8.utils.ExceptionUtils.STATUS_ERROR;
 import static com.android.tools.r8.utils.ExceptionUtils.failWithFakeEntry;
 
 import com.android.tools.r8.Diagnostic;
@@ -358,13 +357,9 @@
       action.run();
     } catch (RetraceFailedException | RetraceAbortException e) {
       // Detail of the errors were already reported
-      System.err.println("Retrace failed");
-      System.exit(STATUS_ERROR);
-      throw null;
+      throw new RuntimeException("Retrace failed", e);
     } catch (Throwable t) {
-      System.err.println("Retrace failed with an internal error.");
-      t.printStackTrace();
-      System.exit(STATUS_ERROR);
+      throw new RuntimeException("Retrace failed with an internal error.", t);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java b/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java
index 9272f58..a632f1c 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java
@@ -123,8 +123,8 @@
       if (parensStart >= line.length()) {
         return null;
       }
-      int parensEnd = firstCharFromIndex(line, parensStart, ')');
-      if (parensEnd >= line.length()) {
+      int parensEnd = line.lastIndexOf(')');
+      if (parensEnd <= parensStart) {
         return null;
       }
       if (firstNonWhiteSpaceCharacterFromIndex(line, parensEnd) == line.length()) {
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java
index fb79984..5790d52 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java
@@ -276,10 +276,10 @@
 
     @Override
     String subExpression() {
-      String anyNonDigitSourceFileChar = anyNonDigitLetterCharWithMarkers + "_ \\.";
-      String anyChar = anyNonDigitSourceFileChar + anyDigit;
-      String colonWithNonDigitSuffix = ":[" + anyNonDigitSourceFileChar + ":" + "]";
-      return "((?:(?:(?:" + colonWithNonDigitSuffix + "))|(?:[" + anyChar + "]))+)?";
+      String anyNonDigitNonColonChar = "^\\d:";
+      String anyNonColonChar = "^:";
+      String colonWithNonDigitSuffix = ":+[" + anyNonDigitNonColonChar + "]";
+      return "((?:(?:(?:" + colonWithNonDigitSuffix + "))|(?:[" + anyNonColonChar + "]))+)?";
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
index cd3e12d..02166f9 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
@@ -116,7 +116,7 @@
     if (newFileName.endsWith("Kt") && (extension.isEmpty() || extension.equals("kt"))) {
       newFileName = newFileName.substring(0, newFileName.length() - 2);
       extension = "kt";
-    } else if (extension.isEmpty()) {
+    } else if (!extension.equals("kt")) {
       extension = "java";
     }
     return newFileName + "." + extension;
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 9706eab..762617e 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -86,7 +86,8 @@
         assert !DexAnnotation.isMemberClassesAnnotation(annotation, dexItemFactory);
         assert !DexAnnotation.isEnclosingMethodAnnotation(annotation, dexItemFactory);
         assert !DexAnnotation.isEnclosingClassAnnotation(annotation, dexItemFactory);
-        assert !DexAnnotation.isSignatureAnnotation(annotation, dexItemFactory);
+        assert appView.options().passthroughDexCode
+            || !DexAnnotation.isSignatureAnnotation(annotation, dexItemFactory);
         if (config.exceptions && DexAnnotation.isThrowingAnnotation(annotation, dexItemFactory)) {
           return true;
         }
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 855e24c..b374e58 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.shaking;
 
 import static com.android.tools.r8.graph.DexEncodedMethod.asProgramMethodOrNull;
+import static com.android.tools.r8.graph.DexEncodedMethod.toMethodDefinitionOrNull;
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult.isOverriding;
 
@@ -1066,7 +1067,7 @@
       case STATIC:
         return lookupStaticTarget(target, context);
       case SUPER:
-        return lookupSuperTarget(target, context);
+        return toMethodDefinitionOrNull(lookupSuperTarget(target, context));
       default:
         return null;
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 72d5af2..c15fdb8 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2858,28 +2858,29 @@
           reason);
     }
     // If invoke target is invalid (inaccessible or not an instance-method) record it and stop.
-    DexEncodedMethod target = resolution.lookupInvokeSuperTarget(from.getHolder(), appInfo);
+    DexClassAndMethod target = resolution.lookupInvokeSuperTarget(from.getHolder(), appInfo);
     if (target == null) {
       failedResolutionTargets.add(resolution.getResolvedMethod().method);
       return;
     }
 
-    DexProgramClass clazz = getProgramClassOrNull(target.getHolderType(), from);
+    DexProgramClass clazz = target.getHolder().asProgramClass();
     if (clazz == null) {
       return;
     }
 
-    ProgramMethod method = new ProgramMethod(clazz, target);
+    ProgramMethod method = target.asProgramMethod();
 
     if (Log.ENABLED) {
-      Log.verbose(getClass(), "Adding super constraint from `%s` to `%s`", from, target.method);
+      Log.verbose(
+          getClass(), "Adding super constraint from `%s` to `%s`", from, target.getReference());
     }
     if (superInvokeDependencies
         .computeIfAbsent(from.getDefinition(), ignore -> ProgramMethodSet.create())
         .add(method)) {
       if (liveMethods.contains(from)) {
         markMethodAsTargeted(method, KeepReason.invokedViaSuperFrom(from));
-        if (!target.accessFlags.isAbstract()) {
+        if (!target.getAccessFlags().isAbstract()) {
           markVirtualMethodAsLive(method, KeepReason.invokedViaSuperFrom(from));
         }
       }
@@ -3671,24 +3672,22 @@
     if (rootSet.noShrinking.containsMethod(singleTarget.getReference())) {
       return;
     }
-    if (methodToKeep != singleTarget) {
+    if (methodToKeep != singleTarget
+        && !syntheticInterfaceMethodBridges.containsKey(methodToKeep.getDefinition().method)) {
+      syntheticInterfaceMethodBridges.put(methodToKeep.getDefinition().method, methodToKeep);
       assert null == methodToKeep.getHolder().lookupMethod(methodToKeep.getDefinition().method);
-      ProgramMethod old =
-          syntheticInterfaceMethodBridges.put(methodToKeep.getDefinition().method, methodToKeep);
-      if (old == null) {
-        if (singleTargetMethod.isLibraryMethodOverride().isTrue()) {
-          methodToKeep.getDefinition().setLibraryMethodOverride(OptionalBool.TRUE);
-        }
-        DexProgramClass singleTargetHolder = singleTarget.getHolder();
-        assert singleTargetHolder.isInterface();
-        markVirtualMethodAsReachable(
-            singleTargetMethod.method,
-            singleTargetHolder.isInterface(),
-            singleTarget,
-            graphReporter.fakeReportShouldNotBeUsed());
-        enqueueMarkMethodLiveAction(
-            singleTarget, singleTarget, graphReporter.fakeReportShouldNotBeUsed());
+      if (singleTargetMethod.isLibraryMethodOverride().isTrue()) {
+        methodToKeep.getDefinition().setLibraryMethodOverride(OptionalBool.TRUE);
       }
+      DexProgramClass singleTargetHolder = singleTarget.getHolder();
+      assert singleTargetHolder.isInterface();
+      markVirtualMethodAsReachable(
+          singleTargetMethod.method,
+          singleTargetHolder.isInterface(),
+          singleTarget,
+          graphReporter.fakeReportShouldNotBeUsed());
+      enqueueMarkMethodLiveAction(
+          singleTarget, singleTarget, graphReporter.fakeReportShouldNotBeUsed());
     }
     action.getAction().accept(builder);
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassFilter.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassFilter.java
index d63374c..88c13ec 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassFilter.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassFilter.java
@@ -73,6 +73,9 @@
                   return TraversalContinuation.CONTINUE;
                 },
                 not(ProguardTypeMatcher::hasSpecificType));
+        if (traversalContinuation.shouldBreak()) {
+          break;
+        }
       }
       if (traversalContinuation.shouldContinue()) {
         nonMatches.add(type);
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 8294be0..47ac1e3 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -104,6 +104,16 @@
   private static final List<String> UNSUPPORTED_FLAG_OPTIONS =
       ImmutableList.of("skipnonpubliclibraryclasses");
 
+  public static ImmutableList<ProguardConfigurationRule> parse(
+      List<ProguardConfigurationSource> sources, DexItemFactory factory, Reporter reporter) {
+    if (sources.isEmpty()) {
+      return ImmutableList.of();
+    }
+    ProguardConfigurationParser parser = new ProguardConfigurationParser(factory, reporter);
+    parser.parse(sources);
+    return ImmutableList.copyOf(parser.getConfig().getRules());
+  }
+
   public ProguardConfigurationParser(
       DexItemFactory dexItemFactory, Reporter reporter) {
     this(dexItemFactory, reporter, false);
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
index 8c5cce7..b5c6375 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.utils.AndroidApp;
 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.Timing;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
@@ -92,8 +93,8 @@
    */
   public static void main(String[] args) {
     if (args.length == 0) {
-      System.err.println(TraceReferencesCommandParser.USAGE_MESSAGE);
-      System.exit(ExceptionUtils.STATUS_ERROR);
+      throw new RuntimeException(
+          StringUtils.joinLines("Invalid invocation.", TraceReferencesCommandParser.USAGE_MESSAGE));
     }
     ExceptionUtils.withMainProgramHandler(() -> run(args));
   }
diff --git a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
index ddd3f78..895f31e 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -411,9 +412,9 @@
 
     @Override
     public void registerInvokeSuper(DexMethod method) {
-      DexEncodedMethod superTarget = appInfo.lookupSuperTarget(method, context);
+      DexClassAndMethod superTarget = appInfo.lookupSuperTarget(method, context);
       if (superTarget != null) {
-        addMethod(superTarget.method);
+        addMethod(superTarget.getReference());
       } else {
         addMethod(method);
       }
@@ -459,12 +460,12 @@
     }
 
     private void registerMethod(ProgramMethod method) {
-      DexEncodedMethod superTarget =
+      DexClassAndMethod superTarget =
           appInfo
               .resolveMethodOn(method.getHolder(), method.getReference())
               .lookupInvokeSpecialTarget(context, appInfo);
       if (superTarget != null) {
-        addMethod(superTarget.method);
+        addMethod(superTarget.getReference());
       }
       for (DexType type : method.getDefinition().parameters().values) {
         registerTypeReference(type);
diff --git a/src/main/java/com/android/tools/r8/utils/BiPredicateUtils.java b/src/main/java/com/android/tools/r8/utils/BiPredicateUtils.java
new file mode 100644
index 0000000..f470d35
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/BiPredicateUtils.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import java.util.function.BiPredicate;
+
+public class BiPredicateUtils {
+
+  public static <S, T> BiPredicate<S, T> alwaysFalse() {
+    return (s, t) -> false;
+  }
+
+  public static <S, T> BiPredicate<S, T> alwaysTrue() {
+    return (s, t) -> true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/ClassMap.java b/src/main/java/com/android/tools/r8/utils/ClassMap.java
index 474f5d0..3c7b99b 100644
--- a/src/main/java/com/android/tools/r8/utils/ClassMap.java
+++ b/src/main/java/com/android/tools/r8/utils/ClassMap.java
@@ -71,10 +71,8 @@
    */
   abstract Supplier<T> getTransparentSupplier(T clazz);
 
-  /**
-   * Kind of the classes supported by this collection.
-   */
-  abstract ClassKind getClassKind();
+  /** Kind of the classes supported by this collection. */
+  abstract ClassKind<?> getClassKind();
 
   @Override
   public String toString() {
diff --git a/src/main/java/com/android/tools/r8/utils/ClassProvider.java b/src/main/java/com/android/tools/r8/utils/ClassProvider.java
index 40d5087..c048529 100644
--- a/src/main/java/com/android/tools/r8/utils/ClassProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/ClassProvider.java
@@ -23,14 +23,14 @@
 
 /** Represents a provider for classes loaded from different sources. */
 public abstract class ClassProvider<T extends DexClass> {
-  private final ClassKind classKind;
+  private final ClassKind<T> classKind;
 
-  ClassProvider(ClassKind classKind) {
+  ClassProvider(ClassKind<T> classKind) {
     this.classKind = classKind;
   }
 
   /** The kind of the classes created by the provider. */
-  final ClassKind getClassKind() {
+  final ClassKind<T> getClassKind() {
     return classKind;
   }
 
@@ -56,13 +56,13 @@
 
   /** Create class provider for java class resource provider. */
   public static <T extends DexClass> ClassProvider<T> forClassFileResources(
-      ClassKind classKind, ClassFileResourceProvider provider, JarApplicationReader reader) {
+      ClassKind<T> classKind, ClassFileResourceProvider provider, JarApplicationReader reader) {
     return new ClassFileResourceReader<>(classKind, provider, reader);
   }
 
   /** Create class provider for preloaded classes, classes may have conflicting names. */
   public static <T extends DexClass> ClassProvider<T> forPreloadedClasses(
-      ClassKind classKind, Collection<T> classes) {
+      ClassKind<T> classKind, Collection<T> classes) {
     ImmutableListMultimap.Builder<DexType, T> builder = ImmutableListMultimap.builder();
     for (T clazz : classes) {
       builder.put(clazz.type, clazz);
@@ -72,17 +72,17 @@
 
   /** Create class provider for preloaded classes. */
   public static <T extends DexClass> ClassProvider<T> combine(
-      ClassKind classKind, List<ClassProvider<T>> providers) {
+      ClassKind<T> classKind, List<ClassProvider<T>> providers) {
     return new CombinedClassProvider<>(classKind, providers);
   }
 
   private static class ClassFileResourceReader<T extends DexClass> extends ClassProvider<T> {
-    private final ClassKind classKind;
+    private final ClassKind<T> classKind;
     private final ClassFileResourceProvider provider;
     private final JarApplicationReader reader;
 
     private ClassFileResourceReader(
-        ClassKind classKind, ClassFileResourceProvider provider, JarApplicationReader reader) {
+        ClassKind<T> classKind, ClassFileResourceProvider provider, JarApplicationReader reader) {
       super(classKind);
       this.classKind = classKind;
       this.provider = provider;
@@ -95,9 +95,9 @@
       ProgramResource resource = provider.getProgramResource(descriptor);
       if (resource != null) {
         try {
-          JarClassFileReader classReader =
-              new JarClassFileReader(reader, classKind.bridgeConsumer(classConsumer));
-          classReader.read(resource, classKind);
+          JarClassFileReader<T> classReader =
+              new JarClassFileReader<>(reader, classConsumer, classKind);
+          classReader.read(resource);
         } catch (ResourceException e) {
           throw new CompilationError("Failed to load class: " + descriptor, e);
         }
@@ -122,7 +122,7 @@
   private static class PreloadedClassProvider<T extends DexClass> extends ClassProvider<T> {
     private final Multimap<DexType, T> classes;
 
-    private PreloadedClassProvider(ClassKind classKind, Multimap<DexType, T> classes) {
+    private PreloadedClassProvider(ClassKind<T> classKind, Multimap<DexType, T> classes) {
       super(classKind);
       this.classes = classes;
     }
@@ -148,7 +148,7 @@
   private static class CombinedClassProvider<T extends DexClass> extends ClassProvider<T> {
     private final List<ClassProvider<T>> providers;
 
-    private CombinedClassProvider(ClassKind classKind, List<ClassProvider<T>> providers) {
+    private CombinedClassProvider(ClassKind<T> classKind, List<ClassProvider<T>> providers) {
       super(classKind);
       this.providers = providers;
     }
diff --git a/src/main/java/com/android/tools/r8/utils/ClasspathClassCollection.java b/src/main/java/com/android/tools/r8/utils/ClasspathClassCollection.java
index 45002d0..28eb17b 100644
--- a/src/main/java/com/android/tools/r8/utils/ClasspathClassCollection.java
+++ b/src/main/java/com/android/tools/r8/utils/ClasspathClassCollection.java
@@ -25,7 +25,7 @@
   }
 
   @Override
-  ClassKind getClassKind() {
+  ClassKind<DexClasspathClass> getClassKind() {
     return ClassKind.CLASSPATH;
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
index 61aa232..8c4da03 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
@@ -25,8 +25,6 @@
 
 public abstract class ExceptionUtils {
 
-  public static final int STATUS_ERROR = 1;
-
   public static void withConsumeResourceHandler(
       Reporter reporter, StringConsumer consumer, String data) {
     withConsumeResourceHandler(reporter, handler -> consumer.accept(data, handler));
@@ -176,23 +174,19 @@
     try {
       action.run();
     } catch (CompilationFailedException e) {
-      throw exitWithError(e, e.getCause());
+      printExitMessage(e.getCause());
+      throw new RuntimeException(e);
     } catch (RuntimeException e) {
-      throw exitWithError(e, e);
+      printExitMessage(e);
+      throw e;
     }
   }
 
-  private static RuntimeException exitWithError(Throwable e, Throwable cause) {
-    if (isExpectedException(cause)) {
-      // Detail of the errors were already reported
-      System.err.println("Compilation failed");
-      System.exit(STATUS_ERROR);
-      throw null;
-    }
-    System.err.println("Compilation failed with an internal error.");
-    e.printStackTrace();
-    System.exit(STATUS_ERROR);
-    throw null;
+  private static void printExitMessage(Throwable cause) {
+    System.err.println(
+        isExpectedException(cause)
+            ? "Compilation failed"
+            : "Compilation failed with an internal error.");
   }
 
   private static boolean isExpectedException(Throwable e) {
diff --git a/src/main/java/com/android/tools/r8/utils/LibraryClassCollection.java b/src/main/java/com/android/tools/r8/utils/LibraryClassCollection.java
index 863cce0..d661a87 100644
--- a/src/main/java/com/android/tools/r8/utils/LibraryClassCollection.java
+++ b/src/main/java/com/android/tools/r8/utils/LibraryClassCollection.java
@@ -30,7 +30,7 @@
   }
 
   @Override
-  ClassKind getClassKind() {
+  ClassKind<DexLibraryClass> getClassKind() {
     return ClassKind.LIBRARY;
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
index 68fa8f3..fff5425 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
@@ -53,7 +53,7 @@
   }
 
   @Override
-  ClassKind getClassKind() {
+  ClassKind<DexProgramClass> getClassKind() {
     return ClassKind.PROGRAM;
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/ValueUtils.java b/src/main/java/com/android/tools/r8/utils/ValueUtils.java
new file mode 100644
index 0000000..8121493
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ValueUtils.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.Value;
+
+public class ValueUtils {
+
+  public static boolean isStringBuilder(Value value, DexItemFactory dexItemFactory) {
+    TypeElement type = value.getType();
+    return type.isClassType()
+        && type.asClassType().getClassType() == dexItemFactory.stringBuilderType;
+  }
+
+  public static boolean isNonNullStringBuilder(Value value, DexItemFactory dexItemFactory) {
+    while (true) {
+      if (value.isPhi()) {
+        return false;
+      }
+
+      Instruction definition = value.getDefinition();
+      if (definition.isNewInstance()) {
+        NewInstance newInstance = definition.asNewInstance();
+        return newInstance.clazz == dexItemFactory.stringBuilderType;
+      }
+
+      if (definition.isInvokeVirtual()) {
+        InvokeVirtual invoke = definition.asInvokeVirtual();
+        if (dexItemFactory.stringBuilderMethods.isAppendMethod(invoke.getInvokedMethod())) {
+          value = invoke.getReceiver();
+          continue;
+        }
+      }
+
+      // Unhandled definition.
+      return false;
+    }
+  }
+}
diff --git a/src/main/keep_retrace.txt b/src/main/keep_retrace.txt
new file mode 100644
index 0000000..6a71f9e
--- /dev/null
+++ b/src/main/keep_retrace.txt
@@ -0,0 +1,15 @@
+# Copyright (c) 2021, 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.
+
+# The retrace api is separated out without repackaging which is why this broad
+# rule is used.
+-keep public class com.android.tools.r8.retrace.* {
+     public <methods>;
+     public <fields>;
+ }
+-keepattributes SourceFile, LineNumberTable, InnerClasses, EnclosingMethod, Exceptions, Signature
+-keepparameternames
+# This is run on r8lib so keep everything in lib that is traced. That way
+# we only need a single mapping file
+-keep,allowshrinking class * { *; }
\ No newline at end of file
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
index 642a4ab..27dbc0f 100644
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
@@ -71,6 +71,7 @@
     List<Path> libraries = new ArrayList<>(1);
     List<Path> classpath = new ArrayList<>(args.length);
     List<Path> mainDexList = new ArrayList<>(1);
+    List<Path> mainDexRules = new ArrayList<>(1);
     List<Path> inputs = new ArrayList<>(args.length);
     for (int i = 0; i < args.length; i++) {
       if (args[i].equals("--lib")) {
@@ -79,6 +80,8 @@
         classpath.add(Paths.get(args[++i]));
       } else if (args[i].equals("--main-dex-list")) {
         mainDexList.add(Paths.get(args[++i]));
+      } else if (args[i].equals("--main-dex-rules")) {
+        mainDexRules.add(Paths.get(args[++i]));
       } else if (isArchive(args[i]) || isClassFile(args[i])) {
         inputs.add(Paths.get(args[i]));
       }
@@ -98,6 +101,9 @@
     if (mainDexList.isEmpty()) {
       throw new RuntimeException("Must supply main-dex-list inputs");
     }
+    if (mainDexRules.isEmpty()) {
+      throw new RuntimeException("Must supply main-dex-rules inputs");
+    }
 
     useProgramFileList(CompilationMode.DEBUG, minApiLevel, libraries, classpath, inputs);
     useProgramFileList(CompilationMode.RELEASE, minApiLevel, libraries, classpath, inputs);
@@ -106,6 +112,8 @@
     useLibraryAndClasspathProvider(minApiLevel, libraries, classpath, inputs);
     useMainDexListFiles(minApiLevel, libraries, classpath, inputs, mainDexList);
     useMainDexClasses(minApiLevel, libraries, classpath, inputs, mainDexList);
+    useMainDexRulesFiles(minApiLevel, libraries, classpath, inputs, mainDexRules);
+    useMainDexRules(minApiLevel, libraries, classpath, inputs, mainDexRules);
     useAssertionConfig(minApiLevel, libraries, classpath, inputs);
     useVArgVariants(minApiLevel, libraries, classpath, inputs, mainDexList);
     incrementalCompileAndMerge(minApiLevel, libraries, classpath, inputs);
@@ -283,6 +291,53 @@
     }
   }
 
+  private static void useMainDexRulesFiles(
+      int minApiLevel,
+      Collection<Path> libraries,
+      Collection<Path> classpath,
+      Collection<Path> inputs,
+      Collection<Path> mainDexRules) {
+    try {
+      D8.run(
+          D8Command.builder(handler)
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(new EnsureOutputConsumer())
+              .addLibraryFiles(libraries)
+              .addClasspathFiles(classpath)
+              .addProgramFiles(inputs)
+              .addMainDexRulesFiles(mainDexRules)
+              .build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected compilation exceptions", e);
+    }
+  }
+
+  private static void useMainDexRules(
+      int minApiLevel,
+      Collection<Path> libraries,
+      Collection<Path> classpath,
+      Collection<Path> inputs,
+      Collection<Path> mainDexRulesFiles) {
+    try {
+      D8Command.Builder builder =
+          D8Command.builder(handler)
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(new EnsureOutputConsumer())
+              .addLibraryFiles(libraries)
+              .addClasspathFiles(classpath)
+              .addProgramFiles(inputs);
+      for (Path mainDexRulesFile : mainDexRulesFiles) {
+        builder.addMainDexRules(
+            Files.readAllLines(mainDexRulesFile), new PathOrigin(mainDexRulesFile));
+      }
+      D8.run(builder.build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected compilation exceptions", e);
+    } catch (IOException e) {
+      throw new RuntimeException("Unexpected IO exception", e);
+    }
+  }
+
   private static void useAssertionConfig(
       int minApiLevel,
       Collection<Path> libraries,
diff --git a/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
index 2c53667..8e33480 100644
--- a/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
+++ b/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
@@ -58,6 +58,9 @@
     Path mainDexList = temp.getRoot().toPath().resolve("maindexlist.txt");
     FileUtils.writeTextFile(mainDexList, "desugaringwithmissingclasstest1/Main.class");
 
+    Path mainDexRules = temp.getRoot().toPath().resolve("maindex.rules");
+    FileUtils.writeTextFile(mainDexRules, "# empty file");
+
     // It is important to place the api usage sample jar after the current classpath because we want
     // to find D8/R8 classes before the ones in the jar, otherwise renamed classes and fields cannot
     // be found.
@@ -67,7 +70,8 @@
             .addAll(
                 ImmutableList.of(
                     ToolHelper.getJavaExecutable(),
-                    "-cp", classPath,
+                    "-cp",
+                    classPath,
                     main,
                     // Compiler arguments.
                     "--output",
@@ -76,9 +80,11 @@
                     Integer.toString(minApiLevel),
                     "--main-dex-list",
                     mainDexList.toString(),
+                    "--main-dex-rules",
+                    mainDexRules.toString(),
                     "--lib",
-                    ToolHelper.getAndroidJar(
-                        AndroidApiLevel.getAndroidApiLevel(minApiLevel)).toString(),
+                    ToolHelper.getAndroidJar(AndroidApiLevel.getAndroidApiLevel(minApiLevel))
+                        .toString(),
                     "--classpath",
                     lib1.toString(),
                     "--classpath",
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 9b80b80..3bcb3a7 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -255,6 +255,21 @@
   }
 
   @Test
+  public void mainDexRules() throws Throwable {
+    Path mainDexRules1 = temp.newFile("main-dex-1.rules").toPath();
+    Path mainDexRules2 = temp.newFile("main-dex-2.rules").toPath();
+    parse("--main-dex-rules", mainDexRules1.toString());
+    parse(
+        "--main-dex-rules", mainDexRules1.toString(), "--main-dex-rules", mainDexRules2.toString());
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void nonExistingMainDexRules() throws Throwable {
+    Path mainDexRules = temp.getRoot().toPath().resolve("main-dex.rules");
+    parse("--main-dex-rules", mainDexRules.toString());
+  }
+
+  @Test
   public void mainDexList() throws Throwable {
     Path mainDexList1 = temp.newFile("main-dex-list-1.txt").toPath();
     Path mainDexList2 = temp.newFile("main-dex-list-2.txt").toPath();
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index 44fe743..4c88d91 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -81,4 +81,9 @@
     }
     return self();
   }
+
+  public D8TestBuilder addMainDexRulesFiles(Path... mainDexRuleFiles) {
+    builder.addMainDexRulesFiles(mainDexRuleFiles);
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 622d595..0e65de1 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -167,8 +167,14 @@
 
   @Override
   public ProguardTestBuilder addProgramClassFileData(Collection<byte[]> classes) {
-    throw new Unimplemented(
-        "No support for adding class files directly (we need to compute the descriptor)");
+    try {
+      Path out = getState().getNewTempFolder().resolve("out.jar");
+      TestBase.writeClassFileDataToJar(out, classes);
+      injars.add(out);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    return self();
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 0b9c896..7157943 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -107,15 +107,14 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 102, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 101, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
-        .withOptionConsumer(opts -> opts.enableClassInlining = true)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 6, "lambdadesugaring"))
         .run();
   }
 
@@ -147,15 +146,14 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 102, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 101, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
         .withMinApiLevel(AndroidApiLevel.N)
-        .withOptionConsumer(opts -> opts.enableClassInlining = true)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 6, "lambdadesugaring"))
         .run();
   }
 
@@ -175,7 +173,6 @@
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
-        .withOptionConsumer(opts -> opts.enableClassInlining = true)
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
@@ -199,7 +196,6 @@
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
         .withMinApiLevel(AndroidApiLevel.N)
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
-        .withOptionConsumer(opts -> opts.enableClassInlining = true)
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index ad512cd..a85ae6a 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -176,6 +176,7 @@
   public static final Path R8LIB_EXCLUDE_DEPS_MAP =
       Paths.get(LIBS_DIR, "r8lib-exclude-deps.jar.map");
   public static final Path DEPS = Paths.get(LIBS_DIR, "deps_all.jar");
+  public static final Path R8_RETRACE_JAR = Paths.get(LIBS_DIR, "r8retrace.jar");
 
   public static final Path DESUGAR_LIB_CONVERSIONS =
       Paths.get(LIBS_DIR, "library_desugar_conversions.zip");
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
index 689e080..4161d87 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.ClassKind;
 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.DexType;
 import com.android.tools.r8.graph.JarApplicationReader;
 import com.android.tools.r8.graph.JarClassFileReader;
@@ -95,8 +96,8 @@
   private void readMethodTemplatesInto(CfCodePrinter codePrinter) throws IOException {
     InternalOptions options = new InternalOptions();
     options.testing.readInputStackMaps = true;
-    JarClassFileReader reader =
-        new JarClassFileReader(
+    JarClassFileReader<DexProgramClass> reader =
+        new JarClassFileReader<>(
             new JarApplicationReader(options),
             clazz -> {
               for (DexEncodedMethod method : clazz.allMethodsSorted()) {
@@ -107,9 +108,10 @@
                     method.getHolderType().getName() + "_" + method.method.name.toString();
                 codePrinter.visitMethod(methodName, method.getCode().asCfCode());
               }
-            });
+            },
+            ClassKind.PROGRAM);
     for (Class<?> clazz : getMethodTemplateClasses()) {
-      reader.read(Origin.unknown(), ClassKind.PROGRAM, ToolHelper.getClassAsBytes(clazz));
+      reader.read(Origin.unknown(), ToolHelper.getClassAsBytes(clazz));
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/IllegalInliningOfMergedConstructorTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/IllegalInliningOfMergedConstructorTest.java
new file mode 100644
index 0000000..bbc415c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/IllegalInliningOfMergedConstructorTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2021, 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.classmerging.horizontal;
+
+import com.android.tools.r8.NeverClassInline;
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IllegalInliningOfMergedConstructorTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withAllRuntimesAndApiLevels().withAllApiLevels().build();
+  }
+
+  public IllegalInliningOfMergedConstructorTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addEnumUnboxingInspector(
+            inspector -> inspector.assertUnboxedIf(parameters.isDexRuntime(), Reprocess.class))
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertMergedInto(B.class, A.class))
+        .addOptionsModification(options -> options.inliningInstructionLimit = 4)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello w0rld!");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new A((System.currentTimeMillis() > 0 ? Reprocess.A : Reprocess.B)).foo();
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    A(Reprocess r) {
+      new B().foo();
+
+      // This will force this constructor to be reprocessed, which will cause the inliner to attempt
+      // to inline B.<init>() into A.<init>(). Since B.<init>() has been moved to A as a result of
+      // horizontal class merging, the inliner will check if B.<init>() is eligible for inlining,
+      // which it is not in this case, due the assignment of $r8$classId.
+      System.out.print(r.ordinal());
+    }
+
+    @NeverInline
+    void foo() {
+      System.out.println("rld!");
+    }
+  }
+
+  @NeverClassInline
+  static class B {
+
+    public B() {}
+
+    @NeverInline
+    void foo() {
+      System.out.print("Hello w");
+    }
+  }
+
+  enum Reprocess {
+    A,
+    B
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonFinalOverrideOfFinalMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonFinalOverrideOfFinalMethodTest.java
new file mode 100644
index 0000000..d73ffcf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonFinalOverrideOfFinalMethodTest.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.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runners.Parameterized;
+
+public class NonFinalOverrideOfFinalMethodTest extends HorizontalClassMergingTestBase {
+
+  @Parameterized.Parameters(name = "{0}, horizontalClassMerging:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.trueValues());
+  }
+
+  public NonFinalOverrideOfFinalMethodTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options ->
+                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .addHorizontallyMergedClassesInspectorIf(
+            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+
+              MethodSubject synchronizedMethodSubject = aClassSubject.uniqueMethodWithName("foo");
+              assertThat(synchronizedMethodSubject, isPresent());
+              assertFalse(synchronizedMethodSubject.isFinal());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A", "B", "BSub");
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      new A().foo();
+      new B().bar();
+      new BSub().foo();
+    }
+  }
+
+  @NeverClassInline
+  public static class A {
+
+    @NeverInline
+    public final void foo() {
+      System.out.println("A");
+    }
+  }
+
+  @NeverClassInline
+  @NoVerticalClassMerging
+  public static class B {
+
+    @NeverInline
+    public void bar() {
+      System.out.println("B");
+    }
+  }
+
+  @NeverClassInline
+  public static class BSub extends B {
+
+    @NeverInline
+    public void foo() {
+      System.out.println("BSub");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/StrictMethodMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/StrictMethodMergingTest.java
new file mode 100644
index 0000000..762824f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/StrictMethodMergingTest.java
@@ -0,0 +1,116 @@
+// 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.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runners.Parameterized;
+
+public class StrictMethodMergingTest extends HorizontalClassMergingTestBase {
+
+  @Parameterized.Parameters(name = "{0}, horizontalClassMerging:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.trueValues());
+  }
+
+  public StrictMethodMergingTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options ->
+                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .addHorizontallyMergedClassesInspectorIf(
+            enableHorizontalClassMerging,
+            inspector ->
+                inspector.assertMergedInto(B.class, A.class).assertMergedInto(D.class, C.class))
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+
+              MethodSubject synchronizedMethodSubject =
+                  aClassSubject.uniqueMethodWithName("m$bridge");
+              assertThat(synchronizedMethodSubject, isPresent());
+              assertTrue(synchronizedMethodSubject.getAccessFlags().isStrict());
+
+              ClassSubject cClassSubject = inspector.clazz(C.class);
+              assertThat(cClassSubject, isPresent());
+
+              MethodSubject unsynchronizedMethodSubject =
+                  cClassSubject.uniqueMethodWithName("m$bridge");
+              assertThat(unsynchronizedMethodSubject, isPresent());
+              assertFalse(unsynchronizedMethodSubject.getAccessFlags().isStrict());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A", "B", "C", "D");
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      new A().m();
+      new B().m();
+      new C().m();
+      new D().m();
+    }
+  }
+
+  @NeverClassInline
+  public static class A {
+
+    @NeverInline
+    public strictfp void m() {
+      System.out.println("A");
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+
+    @NeverInline
+    public strictfp void m() {
+      System.out.println("B");
+    }
+  }
+
+  @NeverClassInline
+  public static class C {
+
+    @NeverInline
+    public void m() {
+      System.out.println("C");
+    }
+  }
+
+  @NeverClassInline
+  public static class D {
+
+    @NeverInline
+    public void m() {
+      System.out.println("D");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedMethodMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedMethodMergingTest.java
new file mode 100644
index 0000000..8d802c4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedMethodMergingTest.java
@@ -0,0 +1,117 @@
+// 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.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runners.Parameterized;
+
+public class SynchronizedMethodMergingTest extends HorizontalClassMergingTestBase {
+
+  @Parameterized.Parameters(name = "{0}, horizontalClassMerging:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.trueValues());
+  }
+
+  public SynchronizedMethodMergingTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options ->
+                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .addHorizontallyMergedClassesInspectorIf(
+            enableHorizontalClassMerging,
+            inspector ->
+                inspector.assertMergedInto(B.class, A.class).assertMergedInto(D.class, C.class))
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+
+              MethodSubject synchronizedMethodSubject =
+                  aClassSubject.uniqueMethodWithName("m$bridge");
+              assertThat(synchronizedMethodSubject, isPresent());
+              assertTrue(synchronizedMethodSubject.isSynchronized());
+
+              ClassSubject cClassSubject = inspector.clazz(C.class);
+              assertThat(cClassSubject, isPresent());
+
+              MethodSubject unsynchronizedMethodSubject =
+                  cClassSubject.uniqueMethodWithName("m$bridge");
+              assertThat(unsynchronizedMethodSubject, isPresent());
+              assertFalse(unsynchronizedMethodSubject.isSynchronized());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A", "B", "C", "D");
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      new A().m();
+      new B().m();
+      new C().m();
+      new D().m();
+    }
+  }
+
+  @NeverClassInline
+  public static class A {
+
+    @NeverInline
+    public synchronized void m() {
+      System.out.println("A");
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+
+    @NeverInline
+    public synchronized void m() {
+      System.out.println("B");
+    }
+  }
+
+  @NeverClassInline
+  public static class C {
+
+    @NeverInline
+    public void m() {
+      System.out.println("C");
+    }
+  }
+
+  @NeverClassInline
+  public static class D {
+
+    @NeverInline
+    public void m() {
+      System.out.println("D");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
index ccfaeaf..5b87397 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -24,11 +24,13 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
+import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.tracereferences.TraceReferences;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.base.Charsets;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
@@ -126,9 +128,13 @@
                   StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
               .setMinApiLevel(apiLevel.getLevel())
               .setOutput(desugaredLib, OutputMode.DexIndexed);
+      Path mapping = null;
       if (shrink) {
-        l8Builder.addProguardConfiguration(
-            Arrays.asList(keepRules.split(System.lineSeparator())), Origin.unknown());
+        mapping = temp.newFolder().toPath().resolve("mapping.txt");
+        List<String> lines =
+            new ArrayList<>(Arrays.asList(keepRules.split(System.lineSeparator())));
+        lines.add("-printmapping " + mapping);
+        l8Builder.addProguardConfiguration(lines, Origin.unknown());
       }
       ToolHelper.runL8(
           l8Builder.build(),
@@ -148,6 +154,15 @@
                             .startsWith(
                                 "Invalid parameter counts in MethodParameter attributes.")));
       }
+      // TODO(b/176900254): The nest check should not be necessary.
+      new CodeInspector(desugaredLib, mapping)
+          .forAllClasses(
+              clazz ->
+                  assertTrue(
+                      clazz.getFinalName().startsWith("j$.")
+                          || clazz
+                              .getOriginalName()
+                              .startsWith(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME)));
       return desugaredLib;
     } catch (Exception e) {
       // Don't wrap assumption violation so junit can catch it.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MonthTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MonthTest.java
new file mode 100644
index 0000000..6b2692b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MonthTest.java
@@ -0,0 +1,88 @@
+// Copyright (c) 2021, 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.desugaredlibrary;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.utils.StringUtils;
+import java.time.Month;
+import java.time.format.TextStyle;
+import java.util.Locale;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MonthTest extends DesugaredLibraryTestBase {
+  private final TestParameters parameters;
+
+  private static final String EXPECTED_JAVA_8_OUTPUT = StringUtils.lines("4");
+  private static final String EXPECTED_JAVA_9_OUTPUT = StringUtils.lines("April");
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public MonthTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private String getExpectedResult(TestParameters parameters) {
+    if (parameters.isCfRuntime()) {
+      if (parameters.getRuntime().asCf().isNewerThan(TestRuntime.CfVm.JDK8)) {
+        return EXPECTED_JAVA_9_OUTPUT;
+      }
+      return EXPECTED_JAVA_8_OUTPUT;
+    }
+    assert parameters.isDexRuntime();
+    // Assumes java.time is desugared only if any library desugaring is required, i.e., on 26.
+    if (requiresAnyCoreLibDesugaring(parameters)) {
+      return EXPECTED_JAVA_8_OUTPUT;
+    }
+    return EXPECTED_JAVA_9_OUTPUT;
+  }
+
+  @Test
+  public void testMonthD8() throws Exception {
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addInnerClasses(MonthTest.class)
+          .run(parameters.getRuntime(), MonthTest.Main.class)
+          .assertSuccessWithOutput(getExpectedResult(parameters));
+      return;
+    }
+    testForD8(parameters.getBackend())
+        .addInnerClasses(MonthTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(getExpectedResult(parameters));
+  }
+
+  @Test
+  public void testMonthR8() throws Exception {
+    Assume.assumeTrue(parameters.isDexRuntime());
+    testForR8(parameters.getBackend())
+        .addInnerClasses(MonthTest.class)
+        .addKeepMainRule(MonthTest.Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(getExpectedResult(parameters));
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      System.out.println(Month.APRIL.getDisplayName(TextStyle.FULL_STANDALONE, Locale.UK));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
index a9590a3..8031f43 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
@@ -115,6 +115,10 @@
     return invokesMethod("int", holder, "hashCode", ImmutableList.of("java.lang.Object"));
   }
 
+  private Matcher<MethodSubject> invokesClassGetClass() {
+    return invokesMethod("java.lang.Class", "java.lang.Object", "getClass", ImmutableList.of());
+  }
+
   private Matcher<MethodSubject> invokesObjectsRequireNonNull(String holder) {
     return invokesMethod(
         "java.lang.Object", holder, "requireNonNull", ImmutableList.of("java.lang.Object"));
@@ -218,7 +222,7 @@
         onlyIf(invokeJavaUtilObjects, invokesObjectsRequireNonNull("java.util.Objects")));
     assertThat(
         testClass.uniqueMethodWithName("objectsRequireNonNull"),
-        onlyIf(invokeJDollarUtilObjects, invokesObjectsRequireNonNull("j$.util.Objects")));
+        onlyIf(parameters.getApiLevel().isLessThan(AndroidApiLevel.K), invokesClassGetClass()));
 
     assertThat(
         testClass.uniqueMethodWithName("objectsRequireNonNullWithMessage"),
@@ -525,7 +529,7 @@
       objectsEquals(args, args);
       objectsHash(1, 2);
       objectsHashCode(4);
-      objectsRequireNonNull(null);
+      objectsRequireNonNull(System.currentTimeMillis() >= 0 ? null : new Object());
       objectsRequireNonNullWithMessage(null, "Was null");
       if (objectsRequireNonNullWithSupplierSupported) {
         objectsRequireNonNullWithSupplier(null, () -> "Supplier said was null");
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
index fcc2124..dc13cb2 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
@@ -30,7 +30,7 @@
 
   private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
   private static final String EXPECTED_RESULT =
-      StringUtils.lines("[5, 6, 7]", "j$.$r8$wrapper$java$util$stream$IntStream$-V-WRP");
+      StringUtils.lines("[5, 6, 7]", "j$.wrappers.$r8$wrapper$java$util$stream$IntStream$-V-WRP");
 
   @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
   public static List<Object[]> data() {
diff --git a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
index d2a5729..330535e 100644
--- a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
+++ b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
@@ -41,15 +42,18 @@
   }
 
   private ObjectToOffsetMapping emptyObjectTObjectMapping() {
-    return new ObjectToOffsetMapping(
+    AppView<AppInfo> appView =
         AppView.createForD8(
             AppInfo.createInitialAppInfo(
                 DexApplication.builder(
                         new InternalOptions(new DexItemFactory(), new Reporter()), null)
-                    .build())),
+                    .build()));
+    return new ObjectToOffsetMapping(
+        appView,
         GraphLens.getIdentityLens(),
         NamingLens.getIdentityLens(),
         InitClassLens.getDefault(),
+        new LensCodeRewriterUtils(appView),
         Collections.emptyList(),
         Collections.emptyList(),
         Collections.emptyList(),
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index 7d22782..603a0b1 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -272,13 +272,13 @@
     DexMethod mOnI3 = factory.createMethod(i3, mProto, m);
     DexMethod mOnI4 = factory.createMethod(i4, mProto, m);
 
-    assertEquals(mOnI0, appInfo.lookupSuperTarget(mOnC0, c1).method);
-    assertEquals(mOnI1, appInfo.lookupSuperTarget(mOnI1, c1).method);
-    assertEquals(mOnI2, appInfo.lookupSuperTarget(mOnI2, c1).method);
+    assertEquals(mOnI0, appInfo.lookupSuperTarget(mOnC0, c1).getReference());
+    assertEquals(mOnI1, appInfo.lookupSuperTarget(mOnI1, c1).getReference());
+    assertEquals(mOnI2, appInfo.lookupSuperTarget(mOnI2, c1).getReference());
 
     assertNull(appInfo.lookupSuperTarget(mOnC1, c2)); // C2 is not a subclass of C1.
-    assertEquals(mOnI1, appInfo.lookupSuperTarget(mOnI3, c2).method);
-    assertEquals(mOnI2, appInfo.lookupSuperTarget(mOnI4, c2).method);
+    assertEquals(mOnI1, appInfo.lookupSuperTarget(mOnI3, c2).getReference());
+    assertEquals(mOnI2, appInfo.lookupSuperTarget(mOnI4, c2).getReference());
 
     // Copy classes to run on the Java VM.
     Path out = temp.newFolder().toPath();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
index 4813749..27f097b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -39,6 +40,7 @@
     return getTestParameters()
         .withCfRuntimes()
         .withDexRuntimesStartingFromExcluding(Version.V4_4_4)
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.L)
         .build();
   }
 
@@ -64,7 +66,7 @@
             .enableInliningAnnotations()
             .addOptionsModification(
                 internalOptions -> internalOptions.enableRedundantConstNumberOptimization = true)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(TestClass.class)
             .assertSuccessWithOutput(expectedOutput);
 
@@ -112,15 +114,10 @@
       assertEquals(1, code.blocks.size());
       // The block only has three instructions.
       BasicBlock entryBlock = code.entryBlock();
-      assertEquals(3, entryBlock.getInstructions().size());
+      assertEquals(2, entryBlock.getInstructions().size());
       // The first one is the `argument` instruction.
       Instruction argument = entryBlock.getInstructions().getFirst();
       assertTrue(argument.isArgument());
-      // The next one is a `const-number` instruction is not used for anything.
-      // TODO(christofferqa): D8 should be able to get rid of the unused const-number instruction.
-      Instruction unused = entryBlock.getInstructions().get(1);
-      assertTrue(unused.isConstNumber());
-      assertEquals(0, unused.outValue().numberOfAllUsers());
       // The `return` instruction returns the argument.
       Instruction ret = entryBlock.getInstructions().getLast();
       assertTrue(ret.isReturn());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ifs/TrivialObjectEqualsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ifs/TrivialObjectEqualsTest.java
new file mode 100644
index 0000000..5e5f50c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ifs/TrivialObjectEqualsTest.java
@@ -0,0 +1,88 @@
+// Copyright (c) 2021, 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.ifs;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class TrivialObjectEqualsTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public TrivialObjectEqualsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector ->
+                assertThat(
+                    inspector.clazz(Main.class).uniqueMethodWithName("dead"),
+                    not(invokesMethodWithName("dead"))))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector ->
+                assertThat(inspector.clazz(Main.class).uniqueMethodWithName("dead"), isAbsent()))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      Object o1 = new Object();
+      Object o2 = new Object();
+      if (o1 == o1) {
+        System.out.print("Hello");
+      } else {
+        dead();
+      }
+      if (o1 == o2) {
+        dead();
+      } else {
+        System.out.println(" world!");
+      }
+    }
+
+    @NeverInline
+    static void dead() {
+      System.out.println("Unexpected!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantInOneConstructorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantInOneConstructorTest.java
index 8dcd7bc..0902756 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantInOneConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantInOneConstructorTest.java
@@ -51,11 +51,7 @@
     ClassSubject testClassSubject = inspector.clazz(TestClass.class);
     assertThat(testClassSubject, isPresent());
     assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
-    if (parameters.isCfRuntime()) {
-      assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
-    } else {
-      assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
-    }
+    assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java
index 9bedfb1..c3a453d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java
@@ -51,11 +51,7 @@
     ClassSubject testClassSubject = inspector.clazz(TestClass.class);
     assertThat(testClassSubject, isPresent());
     assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
-    if (parameters.isCfRuntime()) {
-      assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
-    } else {
-      assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
-    }
+    assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/ObjectsToStringOnLibraryClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/ObjectsToStringOnLibraryClassTest.java
new file mode 100644
index 0000000..3e852df
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/ObjectsToStringOnLibraryClassTest.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2021, 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.string;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+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.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Objects;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ObjectsToStringOnLibraryClassTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ObjectsToStringOnLibraryClassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
+              assertThat(mainMethodSubject, isPresent());
+              assertTrue(
+                  mainMethodSubject
+                      .streamInstructions()
+                      .filter(InstructionSubject::isInvoke)
+                      .allMatch(
+                          x ->
+                              x.getMethod()
+                                  .getName()
+                                  .toSourceString()
+                                  .equals("currentTimeMillis")));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithEmptyOutput();
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      boolean unknown = System.currentTimeMillis() > 0;
+
+      // Boolean.
+      Objects.toString(unknown ? Boolean.FALSE : null);
+      Objects.toString(unknown ? Boolean.TRUE : null);
+
+      // Byte.
+      Objects.toString(unknown ? Byte.valueOf((byte) 0) : null);
+
+      // Char.
+      Objects.toString(unknown ? Character.valueOf((char) 0) : null);
+
+      // Double.
+      Objects.toString(unknown ? Double.valueOf(0) : null);
+
+      // Float.
+      Objects.toString(unknown ? Float.valueOf(0) : null);
+
+      // Integer.
+      Objects.toString(unknown ? Integer.valueOf(0) : null);
+
+      // Long.
+      Objects.toString(unknown ? Long.valueOf(0) : null);
+
+      // Short.
+      Objects.toString(unknown ? Short.valueOf((short) 0) : null);
+
+      // String.
+      Objects.toString(unknown ? "null" : null);
+      Objects.toString(unknown ? new String("null") : null);
+
+      // StringBuffer, StringBuilder.
+      Objects.toString(unknown ? new StringBuffer() : null);
+      Objects.toString(unknown ? new StringBuilder() : null);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithIfNullUserTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithIfNullUserTest.java
new file mode 100644
index 0000000..2d4b414
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithIfNullUserTest.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2021, 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.string;
+
+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 StringBuilderWithIfNullUserTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public StringBuilderWithIfNullUserTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(StringBuilderWithIfNullUserTest.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      StringBuilder builder = new StringBuilder();
+      builder.append("Hello world!");
+      if (builder != null) {
+        System.out.println(builder.toString());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithIfUserTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithIfUserTest.java
new file mode 100644
index 0000000..14ba197
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithIfUserTest.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2021, 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.string;
+
+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 StringBuilderWithIfUserTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public StringBuilderWithIfUserTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(StringBuilderWithIfUserTest.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      StringBuilder builder = new StringBuilder();
+      StringBuilder other = System.currentTimeMillis() > 0 ? new StringBuilder() : builder;
+      builder.append("Hello world!");
+      if (builder != other) {
+        System.out.println(builder.toString());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendObjectTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendObjectTest.java
index d8ed11c..b8c0799 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendObjectTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendObjectTest.java
@@ -5,13 +5,14 @@
 package com.android.tools.r8.ir.optimize.string;
 
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.instantiatesClass;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 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.MethodSubject;
+import java.util.Objects;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -40,11 +41,7 @@
         .inspect(
             inspector -> {
               MethodSubject mainMethod = inspector.clazz(Main.class).mainMethod();
-              assertThat(
-                  mainMethod,
-                  notIf(
-                      instantiatesClass(StringBuilder.class),
-                      canUseJavaUtilObjects(parameters) || parameters.isDexRuntime()));
+              assertThat(mainMethod, not(instantiatesClass(StringBuilder.class)));
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithEmptyOutput();
@@ -55,6 +52,8 @@
     public static void main(String[] args) {
       A a = System.currentTimeMillis() > 0 ? new A() : null;
       new StringBuilder().append(a).toString();
+      Objects.toString(new StringBuilder().append(a));
+      String.valueOf(new StringBuilder().append(a));
     }
   }
 
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 6ef399e..52b60fe 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -358,6 +358,26 @@
             .sorted()
             .collect(Collectors.toList());
 
+    // Build main-dex list using D8 & rules.
+    List<String> mainDexListFromD8;
+    {
+      final Box<String> mainDexListOutputFromD8 = new Box<>();
+      testForD8(Backend.DEX)
+          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K))
+          .addProgramFiles(inputJar)
+          .addProgramFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
+          .addMainDexRulesFiles(mainDexRules)
+          .setMainDexListConsumer(ToolHelper.consumeString(mainDexListOutputFromD8::set))
+          .setMinApi(minSdk)
+          .allowStdoutMessages()
+          .compile();
+      mainDexListFromD8 =
+          StringUtils.splitLines(mainDexListOutputFromD8.get()).stream()
+              .map(this::mainDexStringToDescriptor)
+              .sorted()
+              .collect(Collectors.toList());
+    }
+
     // Build main-dex list using R8.
     final Box<String> r8MainDexListOutput = new Box<>();
     testForR8(Backend.DEX)
@@ -402,6 +422,13 @@
     }
     String[] refList = new String(Files.readAllBytes(
         expectedMainDexList), StandardCharsets.UTF_8).split("\n");
+    for (int i = 0; i < refList.length; i++) {
+      String reference = refList[i].trim();
+      if (mainDexListFromD8.size() <= i) {
+        fail("D8 main-dex list is missing '" + reference + "'");
+      }
+      checkSameMainDexEntry(reference, mainDexListFromD8.get(i));
+    }
     int nonLambdaOffset = 0;
     for (int i = 0; i < refList.length; i++) {
       String reference = refList[i].trim();
diff --git a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
index d9b3cd4..61387ea 100644
--- a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
+++ b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
@@ -91,31 +91,37 @@
     assumeTrue(backend == Backend.DEX);
     validateSequence(
         methodThrowToBeInlined.iterateInstructions(),
+
         // 'if' with "foo#1" is flipped.
         InstructionSubject::isIfEqz,
 
         // 'if' with "foo#2" is removed along with the constant.
 
-        // 'if' with "foo#3" is removed so now we have unconditional call.
-        insn -> insn.isConstString("StringConstants::foo#3", JumboStringMode.DISALLOW),
-        InstructionSubject::isInvokeStatic,
-        InstructionSubject::isThrow,
+        // 'if' with "foo#3" is removed so now we have an unconditional call inside the branch.
+        InstructionSubject::isIfEq,
 
-        // 'if's with "foo#4" and "foo#5" are flipped, but their throwing branches
-        // are not moved to the end of the code (area for improvement?).
+        // 'if' with "foo#4" is flipped, but the throwing branch is not moved to the end of the code
+        // (area for improvement?).
         insn -> insn.isConstString("StringConstants::foo#4", JumboStringMode.DISALLOW),
         InstructionSubject::isIfEqz, // Flipped if
         InstructionSubject::isGoto, // Jump around throwing branch.
         InstructionSubject::isInvokeStatic, // Throwing branch.
         InstructionSubject::isThrow,
+
+        // 'if's with "foo#5" are flipped.
         insn -> insn.isConstString("StringConstants::foo#5", JumboStringMode.DISALLOW),
         InstructionSubject::isIfEqz, // Flipped if
         InstructionSubject::isReturnVoid, // Final return statement.
         InstructionSubject::isInvokeStatic, // Throwing branch.
         InstructionSubject::isThrow,
 
-        // After 'if' with "foo#1" flipped, always throwing branch
-        // moved here along with the constant.
+        // 'if' with "foo#3" is removed so now we have an unconditional call.
+        insn -> insn.isConstString("StringConstants::foo#3", JumboStringMode.DISALLOW),
+        InstructionSubject::isInvokeStatic,
+        InstructionSubject::isThrow,
+
+        // After 'if' with "foo#1" flipped, the always throwing branch is moved here along with the
+        // constant.
         insn -> insn.isConstString("StringConstants::foo#1", JumboStringMode.DISALLOW),
         InstructionSubject::isInvokeStatic,
         InstructionSubject::isThrow);
diff --git a/src/test/java/com/android/tools/r8/naming/keeppackagenames/KeepPackageNameRootTest.java b/src/test/java/com/android/tools/r8/naming/keeppackagenames/KeepPackageNameRootTest.java
new file mode 100644
index 0000000..309ab55
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/keeppackagenames/KeepPackageNameRootTest.java
@@ -0,0 +1,74 @@
+// 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.keeppackagenames;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.ProguardVersion;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestShrinkerBuilder;
+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 KeepPackageNameRootTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public KeepPackageNameRootTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8Compat() throws Exception {
+    run(testForR8Compat(Backend.CF));
+  }
+
+  @Test
+  public void testR8Full() throws Exception {
+    run(testForR8(Backend.CF));
+  }
+
+  @Test
+  public void testR8PG() throws Exception {
+    run(testForProguard(ProguardVersion.V7_0_0).addKeepRules("-dontwarn"));
+  }
+
+  private TestCompileResult<?, ?> run(TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder)
+      throws Exception {
+    return testBuilder
+        .addProgramClassFileData(
+            transformer(Main.class)
+                .setClassDescriptor("Lfoo/Main;")
+                .removeInnerClasses()
+                .transform())
+        .addKeepRules("-keeppackagenames foo.**")
+        .addKeepClassRulesWithAllowObfuscation("foo.Main")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              assertEquals(1, inspector.allClasses().size());
+              inspector.forAllClasses(
+                  clazz -> {
+                    assertNotEquals("foo", clazz.getDexProgramClass().getType().getPackageName());
+                  });
+            });
+  }
+
+  /* Will be in package foo */
+  public static class Main {}
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageCustomMethodHandleTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageCustomMethodHandleTest.java
new file mode 100644
index 0000000..f20603d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageCustomMethodHandleTest.java
@@ -0,0 +1,162 @@
+// Copyright (c) 2021, 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.repackage;
+
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.lang.invoke.CallSite;
+import java.lang.invoke.ConstantCallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+@RunWith(Parameterized.class)
+public class RepackageCustomMethodHandleTest extends RepackageTestBase {
+
+  private static final String EXPECTED = "InvokeCustom::foo";
+
+  @Parameters(name = "{1}, kind: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+        getTestParameters()
+            .withCfRuntimes()
+            .withDexRuntimesStartingFromIncluding(Version.V8_1_0)
+            .withApiLevelsStartingAtIncluding(AndroidApiLevel.O_MR1)
+            .build());
+  }
+
+  public RepackageCustomMethodHandleTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(InvokeCustom.class)
+        .addProgramClassFileData(
+            transformer(Main.class).addClassTransformer(generateCallSiteInvoke()).transform())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test()
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(InvokeCustom.class)
+        .addProgramClassFileData(
+            transformer(Main.class).addClassTransformer(generateCallSiteInvoke()).transform())
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRules(Main.class)
+        .apply(this::configureRepackaging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(inspector -> assertThat(InvokeCustom.class, isRepackaged(inspector)))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  private ClassTransformer generateCallSiteInvoke() {
+    return new ClassTransformer() {
+      @Override
+      public void visit(
+          int version,
+          int access,
+          String name,
+          String signature,
+          String superName,
+          String[] interfaces) {
+        super.visit(version, access, name, signature, superName, interfaces);
+        MethodVisitor mv =
+            cv.visitMethod(
+                Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC,
+                "main",
+                "([Ljava/lang/String;)V",
+                null,
+                null);
+        MethodType mt =
+            MethodType.methodType(
+                CallSite.class,
+                MethodHandles.Lookup.class,
+                String.class,
+                MethodType.class,
+                MethodHandle.class);
+        Handle bootstrap =
+            new Handle(
+                Opcodes.H_INVOKESTATIC,
+                Type.getInternalName(InvokeCustom.class),
+                "createCallSite",
+                mt.toMethodDescriptorString(),
+                false);
+        mv.visitTypeInsn(Opcodes.NEW, Type.getInternalName(InvokeCustom.class));
+        mv.visitInsn(Opcodes.DUP);
+        mv.visitMethodInsn(
+            Opcodes.INVOKESPECIAL,
+            Type.getInternalName(InvokeCustom.class),
+            "<init>",
+            "()V",
+            false);
+        mv.visitInvokeDynamicInsn(
+            "foo",
+            "(" + DescriptorUtils.javaTypeToDescriptor(InvokeCustom.class.getTypeName()) + ")V",
+            bootstrap,
+            new Handle(
+                Opcodes.H_INVOKEVIRTUAL,
+                Type.getInternalName(InvokeCustom.class),
+                "foo",
+                "()V",
+                false));
+        mv.visitInsn(Opcodes.RETURN);
+        mv.visitMaxs(10, 10);
+      }
+    };
+  }
+
+  @NeverClassInline
+  public static class InvokeCustom {
+
+    @NeverInline
+    public void foo() {
+      System.out.println("InvokeCustom::foo");
+    }
+
+    public static CallSite createCallSite(
+        MethodHandles.Lookup caller, String name, MethodType type, MethodHandle mh) {
+      return new ConstantCallSite(mh);
+    }
+  }
+
+  public static class Main {
+
+    /*
+    public static void main(String[] args) {
+      CallSite cs = { InvokeCustom::foo, args: InvokeCustom }
+      cs.invoke(new InvokeCustom());
+    }
+     */
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java b/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java
index 8f4ec7b..74f834d 100644
--- a/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java
@@ -18,7 +18,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ResolutionResult;
@@ -62,10 +62,10 @@
     assertTrue(resolutionResult.isSingleResolution());
     DexProgramClass context =
         appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
-    DexEncodedMethod lookedUpMethod =
+    DexClassAndMethod lookedUpMethod =
         resolutionResult.lookupInvokeSuperTarget(context, appView.appInfo());
     assertNotNull(lookedUpMethod);
-    assertEquals(lookedUpMethod.method, method);
+    assertEquals(lookedUpMethod.getReference(), method);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
index 6f14877..3f7568a 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ResolutionResult;
@@ -131,13 +131,13 @@
     assertEquals(
         OptionalBool.of(inSameNest),
         resolutionResult.isAccessibleFrom(callerClassDefinition, appInfo));
-    DexEncodedMethod targetSpecial =
+    DexClassAndMethod targetSpecial =
         resolutionResult.lookupInvokeSpecialTarget(callerClassDefinition, appInfo);
-    DexEncodedMethod targetSuper =
+    DexClassAndMethod targetSuper =
         resolutionResult.lookupInvokeSuperTarget(callerClassDefinition, appInfo);
     if (inSameNest) {
       assertEquals(definingClassDefinition.type, targetSpecial.getHolderType());
-      assertEquals(targetSpecial, targetSuper);
+      assertEquals(targetSpecial.getReference(), targetSuper.getReference());
     } else {
       assertNull(targetSpecial);
       assertNull(targetSuper);
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java
index 6c7e5f2..5a3d01e 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.TestRunResult;
 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.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ResolutionResult;
@@ -107,13 +107,13 @@
         resolutionResult.isAccessibleFrom(callerClassDefinition, appInfo));
 
     // Verify that looking up the dispatch target returns the defining method.
-    DexEncodedMethod targetSpecial =
+    DexClassAndMethod targetSpecial =
         resolutionResult.lookupInvokeSpecialTarget(callerClassDefinition, appInfo);
-    DexEncodedMethod targetSuper =
+    DexClassAndMethod targetSuper =
         resolutionResult.lookupInvokeSuperTarget(callerClassDefinition, appInfo);
     if (inSameNest) {
       assertEquals(definingClassDefinition.type, targetSpecial.getHolderType());
-      assertEquals(targetSpecial, targetSuper);
+      assertEquals(targetSpecial.getReference(), targetSuper.getReference());
     } else {
       assertNull(targetSpecial);
       assertNull(targetSuper);
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
index fe17f34..b291f91 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
@@ -5,7 +5,6 @@
 
 import static com.android.tools.r8.TestRuntime.CfVm.JDK11;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
@@ -14,7 +13,7 @@
 import com.android.tools.r8.TestRunResult;
 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.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ResolutionResult;
@@ -140,21 +139,16 @@
 
     // Verify that looking up the dispatch target returns a valid target
     // iff in the same nest and declaredHolder == definingHolder.
-    DexEncodedMethod targetSpecial =
+    DexClassAndMethod targetSpecial =
         resolutionResult.lookupInvokeSpecialTarget(callerClassDefinition, appInfo);
-    DexEncodedMethod targetSuper =
+    DexClassAndMethod targetSuper =
         resolutionResult.lookupInvokeSuperTarget(callerClassDefinition, appInfo);
-    if (inSameNest && symbolicReferenceIsDefiningType) {
+    if (inSameNest) {
       assertEquals(definingClassDefinition.type, targetSpecial.getHolderType());
-      assertEquals(targetSpecial, targetSuper);
+      assertEquals(targetSpecial.getReference(), targetSuper.getReference());
     } else {
       assertNull(targetSpecial);
-      if (!inSameNest) {
-        assertNull(targetSuper);
-      } else {
-        // TODO(b/145775365): The current invoke-super will return the resolution target.
-        assertNotNull(targetSuper);
-      }
+      assertNull(targetSuper);
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodPublicAccessWithIntermediateTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodPublicAccessWithIntermediateTest.java
index 9aa12b3..7d30885 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodPublicAccessWithIntermediateTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodPublicAccessWithIntermediateTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.TestRunResult;
 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.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ResolutionResult;
@@ -108,13 +108,13 @@
         OptionalBool.TRUE, resolutionResult.isAccessibleFrom(callerClassDefinition, appInfo));
 
     // Verify that looking up the dispatch target returns the defining method.
-    DexEncodedMethod targetSpecial =
+    DexClassAndMethod targetSpecial =
         resolutionResult.lookupInvokeSpecialTarget(callerClassDefinition, appInfo);
     assertEquals(definingClassDefinition.type, targetSpecial.getHolderType());
 
-    DexEncodedMethod targetSuper =
+    DexClassAndMethod targetSuper =
         resolutionResult.lookupInvokeSuperTarget(callerClassDefinition, appInfo);
-    assertEquals(targetSpecial, targetSuper);
+    assertEquals(targetSpecial.getReference(), targetSuper.getReference());
   }
 
   private void assertCallingClassCallsTarget(
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
index 2074afa..cd839f1 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
@@ -9,6 +9,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -17,6 +18,7 @@
 import com.android.tools.r8.retrace.stacktraces.ActualRetraceBotStackTraceWithInfo;
 import com.android.tools.r8.retrace.stacktraces.FoundMethodVerboseStackTrace;
 import com.android.tools.r8.retrace.stacktraces.PGStackTrace;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.base.Charsets;
 import java.io.ByteArrayOutputStream;
@@ -36,16 +38,30 @@
 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 RetraceCommandLineTests {
 
-  private static final boolean testExternal = false;
+  private static String SMILEY_EMOJI = "\uD83D\uDE00";
+
   private static final String WAITING_MESSAGE =
       "Waiting for stack-trace input..." + StringUtils.LINE_SEPARATOR;
 
   @Rule public TemporaryFolder folder = new TemporaryFolder();
 
-  private static String SMILEY_EMOJI = "\uD83D\uDE00";
+  private final boolean testExternal;
+
+  @Parameters(name = "{0}")
+  public static Boolean[] data() {
+    return BooleanUtils.values();
+  }
+
+  public RetraceCommandLineTests(boolean testExternal) {
+    this.testExternal = testExternal;
+  }
 
   @Test
   public void testPrintIdentityStackTraceFile() throws IOException {
@@ -233,11 +249,14 @@
   private ProcessResult runRetraceCommandLine(File stdInput, Collection<String> args)
       throws IOException {
     if (testExternal) {
+      // The external dependency is built on top of R8Lib. If test.py is run with
+      // no r8lib, do not try and run the external R8 Retrace since it has not been built.
+      assumeTrue(Files.exists(ToolHelper.R8LIB_JAR));
       List<String> command = new ArrayList<>();
       command.add(ToolHelper.getSystemJavaExecutable());
       command.add("-ea");
       command.add("-cp");
-      command.add(ToolHelper.R8_JAR.toString());
+      command.add(ToolHelper.R8_RETRACE_JAR.toString());
       command.add("com.android.tools.r8.retrace.Retrace");
       command.addAll(args);
       ProcessBuilder builder = new ProcessBuilder(command);
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java
index 878a91d..4f79193 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java
@@ -297,7 +297,7 @@
 
           @Override
           public List<String> retracedStackTrace() {
-            return Collections.singletonList("foo.Bar$Baz.baz(Bar.dummy)");
+            return Collections.singletonList("foo.Bar$Baz.baz(Bar.java)");
           }
 
           @Override
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 e8e09aa..94abc05 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -10,10 +10,13 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.retrace.internal.RetraceAbortException;
 import com.android.tools.r8.retrace.stacktraces.ActualBotStackTraceBase;
 import com.android.tools.r8.retrace.stacktraces.ActualIdentityStackTrace;
@@ -22,6 +25,7 @@
 import com.android.tools.r8.retrace.stacktraces.AmbiguousStackTrace;
 import com.android.tools.r8.retrace.stacktraces.AmbiguousWithMultipleLineMappingsStackTrace;
 import com.android.tools.r8.retrace.stacktraces.AmbiguousWithSignatureNonVerboseStackTrace;
+import com.android.tools.r8.retrace.stacktraces.AutoStackTrace;
 import com.android.tools.r8.retrace.stacktraces.CircularReferenceStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ColonInFileNameStackTrace;
 import com.android.tools.r8.retrace.stacktraces.FileNameExtensionStackTrace;
@@ -46,7 +50,12 @@
 import com.android.tools.r8.retrace.stacktraces.UnicodeInFileNameStackTrace;
 import com.android.tools.r8.retrace.stacktraces.UnknownSourceStackTrace;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.function.Consumer;
@@ -59,44 +68,49 @@
 @RunWith(Parameterized.class)
 public class RetraceTests extends TestBase {
 
-  @Parameters(name = "{0}, use regular expression: {1}")
+  @Parameters(name = "{0}, use regular expression: {1}, external: {2}")
   public static Collection<Object[]> data() {
-    return buildParameters(getTestParameters().withNoneRuntime().build(), BooleanUtils.values());
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), BooleanUtils.values(), BooleanUtils.values());
   }
 
+  private final TestParameters testParameters;
   private final boolean useRegExpParsing;
+  private final boolean external;
 
-  public RetraceTests(TestParameters parameters, boolean useRegExpParsing) {
+  public RetraceTests(TestParameters parameters, boolean useRegExpParsing, boolean external) {
+    this.testParameters = parameters;
     this.useRegExpParsing = useRegExpParsing;
+    this.external = external;
   }
 
   @Test
-  public void testCanMapExceptionClass() {
+  public void testCanMapExceptionClass() throws Exception {
     runRetraceTest(new ObfucatedExceptionClassStackTrace());
   }
 
   @Test
-  public void testSuppressedStackTrace() {
+  public void testSuppressedStackTrace() throws Exception {
     runRetraceTest(new SuppressedStackTrace());
   }
 
   @Test
-  public void testFileNameStackTrace() {
+  public void testFileNameStackTrace() throws Exception {
     runRetraceTest(new FileNameExtensionStackTrace());
   }
 
   @Test
-  public void testInlineFileNameStackTrace() {
+  public void testInlineFileNameStackTrace() throws Exception {
     runRetraceTest(new InlineFileNameStackTrace());
   }
 
   @Test
-  public void testInlineFileNameWithInnerClassesStackTrace() {
+  public void testInlineFileNameWithInnerClassesStackTrace() throws Exception {
     runRetraceTest(new InlineFileNameWithInnerClassesStackTrace());
   }
 
   @Test
-  public void testNoObfuscationRangeMappingWithStackTrace() {
+  public void testNoObfuscationRangeMappingWithStackTrace() throws Exception {
     runRetraceTest(new NoObfuscationRangeMappingWithStackTrace());
   }
 
@@ -123,18 +137,18 @@
   }
 
   @Test
-  public void testInvalidStackTraceLineWarnings() {
+  public void testInvalidStackTraceLineWarnings() throws Exception {
     InvalidStackTrace invalidStackTraceTest = new InvalidStackTrace();
     runRetraceTest(invalidStackTraceTest).assertNoMessages();
   }
 
   @Test
-  public void testAssertionErrorInRetrace() {
+  public void testAssertionErrorInRetrace() throws Exception {
     runRetraceTest(new RetraceAssertionErrorStackTrace());
   }
 
   @Test
-  public void testActualStackTraces() {
+  public void testActualStackTraces() throws Exception {
     List<ActualBotStackTraceBase> stackTraces =
         ImmutableList.of(new ActualIdentityStackTrace(), new ActualRetraceBotStackTrace());
     for (ActualBotStackTraceBase stackTrace : stackTraces) {
@@ -144,37 +158,37 @@
   }
 
   @Test
-  public void testAmbiguousStackTrace() {
+  public void testAmbiguousStackTrace() throws Exception {
     runRetraceTest(new AmbiguousStackTrace());
   }
 
   @Test
-  public void testAmbiguousMissingLineStackTrace() {
+  public void testAmbiguousMissingLineStackTrace() throws Exception {
     runRetraceTest(new AmbiguousMissingLineStackTrace());
   }
 
   @Test
-  public void testAmbiguousMissingLineNotVerbose() {
+  public void testAmbiguousMissingLineNotVerbose() throws Exception {
     runRetraceTest(new AmbiguousWithSignatureNonVerboseStackTrace());
   }
 
   @Test
-  public void testAmbiguousMultipleMappingsTest() {
+  public void testAmbiguousMultipleMappingsTest() throws Exception {
     runRetraceTest(new AmbiguousWithMultipleLineMappingsStackTrace());
   }
 
   @Test
-  public void testInliningWithLineNumbers() {
+  public void testInliningWithLineNumbers() throws Exception {
     runRetraceTest(new InlineWithLineNumbersStackTrace());
   }
 
   @Test
-  public void testInliningNoLineNumberInfoStackTraces() {
+  public void testInliningNoLineNumberInfoStackTraces() throws Exception {
     runRetraceTest(new InlineNoLineNumberStackTrace());
   }
 
   @Test
-  public void testCircularReferenceStackTrace() {
+  public void testCircularReferenceStackTrace() throws Exception {
     // Proguard retrace (and therefore the default regular expression) will not retrace circular
     // reference exceptions.
     assumeFalse(useRegExpParsing);
@@ -182,76 +196,116 @@
   }
 
   @Test
-  public void testObfuscatedRangeToSingleLine() {
+  public void testObfuscatedRangeToSingleLine() throws Exception {
     runRetraceTest(new ObfuscatedRangeToSingleLineStackTrace());
   }
 
   @Test
   @Ignore("b/170293908")
-  public void testBootLoaderAndNamedModulesStackTrace() {
+  public void testBootLoaderAndNamedModulesStackTrace() throws Exception {
     assumeFalse(useRegExpParsing);
     runRetraceTest(new NamedModuleStackTrace());
   }
 
   @Test
-  public void testUnknownSourceStackTrace() {
+  public void testUnknownSourceStackTrace() throws Exception {
     runRetraceTest(new UnknownSourceStackTrace());
   }
 
   @Test
-  public void testInlineSourceFileContext() {
+  public void testInlineSourceFileContext() throws Exception {
     runRetraceTest(new InlineSourceFileContextStackTrace());
   }
 
   @Test
-  public void testColonInSourceFileNameStackTrace() {
+  public void testColonInSourceFileNameStackTrace() throws Exception {
     runRetraceTest(new ColonInFileNameStackTrace());
   }
 
   @Test
-  public void testMultipleDotsInFileNameStackTrace() {
+  public void testMultipleDotsInFileNameStackTrace() throws Exception {
     runRetraceTest(new MultipleDotsInFileNameStackTrace());
   }
 
   @Test
-  public void testUnicodeInFileNameStackTrace() {
+  public void testUnicodeInFileNameStackTrace() throws Exception {
     runRetraceTest(new UnicodeInFileNameStackTrace());
   }
 
   @Test
-  public void testMemberFieldOverlapStackTrace() {
+  public void testMemberFieldOverlapStackTrace() throws Exception {
     MemberFieldOverlapStackTrace stackTraceForTest = new MemberFieldOverlapStackTrace();
     runRetraceTest(stackTraceForTest);
     inspectRetraceTest(stackTraceForTest, stackTraceForTest::inspectField);
   }
 
   @Test
-  public void testSourceFileWithNumberAndEmptyStackTrace() {
+  public void testSourceFileWithNumberAndEmptyStackTrace() throws Exception {
     runRetraceTest(new SourceFileWithNumberAndEmptyStackTrace());
   }
 
   @Test
-  public void testSourceFileNameSynthesizeStackTrace() {
+  public void testSourceFileNameSynthesizeStackTrace() throws Exception {
     runRetraceTest(new SourceFileNameSynthesizeStackTrace());
   }
 
+  @Test
+  public void testAutoStackTrace() throws Exception {
+    runRetraceTest(new AutoStackTrace());
+  }
+
   private void inspectRetraceTest(
       StackTraceForTest stackTraceForTest, Consumer<Retracer> inspection) {
     inspection.accept(
         Retracer.createDefault(stackTraceForTest::mapping, new TestDiagnosticMessagesImpl()));
   }
 
-  private TestDiagnosticMessagesImpl runRetraceTest(StackTraceForTest stackTraceForTest) {
-    TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
-    RetraceCommand retraceCommand =
-        RetraceCommand.builder(diagnosticsHandler)
-            .setProguardMapProducer(stackTraceForTest::mapping)
-            .setStackTrace(stackTraceForTest.obfuscatedStackTrace())
-            .setRegularExpression(useRegExpParsing ? DEFAULT_REGULAR_EXPRESSION : null)
-            .setRetracedStackTraceConsumer(
-                retraced -> assertEquals(stackTraceForTest.retracedStackTrace(), retraced))
-            .build();
-    Retrace.run(retraceCommand);
-    return diagnosticsHandler;
+  private TestDiagnosticMessagesImpl runRetraceTest(StackTraceForTest stackTraceForTest)
+      throws Exception {
+    if (external) {
+      assumeTrue(useRegExpParsing);
+      assumeTrue(testParameters.isCfRuntime());
+      // The external dependency is built on top of R8Lib. If test.py is run with
+      // no r8lib, do not try and run the external R8 Retrace since it has not been built.
+      assumeTrue(Files.exists(ToolHelper.R8LIB_JAR));
+      Path path = temp.newFolder().toPath();
+      Path mappingFile = path.resolve("mapping");
+      Files.write(mappingFile, stackTraceForTest.mapping().getBytes());
+      Path stackTraceFile = path.resolve("stacktrace.txt");
+      Files.write(
+          stackTraceFile,
+          StringUtils.joinLines(stackTraceForTest.obfuscatedStackTrace())
+              .getBytes(StandardCharsets.UTF_8));
+
+      List<String> command = new ArrayList<>();
+      command.add(testParameters.getRuntime().asCf().getJavaExecutable().toString());
+      command.add("-ea");
+      command.add("-cp");
+      command.add(ToolHelper.R8_RETRACE_JAR.toString());
+      command.add("com.android.tools.r8.retrace.Retrace");
+      command.add(mappingFile.toString());
+      command.add(stackTraceFile.toString());
+      command.add("-quiet");
+      ProcessBuilder builder = new ProcessBuilder(command);
+      ProcessResult processResult = ToolHelper.runProcess(builder);
+      assertEquals(
+          StringUtils.joinLines(stackTraceForTest.retracedStackTrace())
+              + StringUtils.LINE_SEPARATOR,
+          processResult.stdout);
+      // TODO(b/177204438): Parse diagnostics from stdErr
+      return new TestDiagnosticMessagesImpl();
+    } else {
+      TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
+      RetraceCommand retraceCommand =
+          RetraceCommand.builder(diagnosticsHandler)
+              .setProguardMapProducer(stackTraceForTest::mapping)
+              .setStackTrace(stackTraceForTest.obfuscatedStackTrace())
+              .setRegularExpression(useRegExpParsing ? DEFAULT_REGULAR_EXPRESSION : null)
+              .setRetracedStackTraceConsumer(
+                  retraced -> assertEquals(stackTraceForTest.retracedStackTrace(), retraced))
+              .build();
+      Retrace.run(retraceCommand);
+      return diagnosticsHandler;
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AutoStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AutoStackTrace.java
new file mode 100644
index 0000000..0504b58
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AutoStackTrace.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2021, 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 com.google.common.collect.ImmutableList;
+import java.util.List;
+
+public class AutoStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return ImmutableList.of(
+        "java.io.IOException: INVALID_SENDER",
+        "\tat qtr.a(:com.google.android.gms@203915081@20.39.15 (060808-335085812):46)",
+        "\tat qtr.a(:com.google.android.gms@203915081@20.39.15 (060808-335085812):18)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "com.android.tools.r8.AutoTest -> qtr:",
+        "  46:46:void foo(int):200:200 -> a",
+        "  17:19:void foo(int,int):23:25 -> a");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return ImmutableList.of(
+        "java.io.IOException: INVALID_SENDER",
+        "\tat com.android.tools.r8.AutoTest.foo(AutoTest.java:200)",
+        "\tat com.android.tools.r8.AutoTest.foo(AutoTest.java:24)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/FileNameExtensionStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/FileNameExtensionStackTrace.java
index ae9e0ff..dc77ca6 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/FileNameExtensionStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/FileNameExtensionStackTrace.java
@@ -41,7 +41,7 @@
         "    at R8.main(Native Method)",
         "    at R8.main(R8.java:)",
         "    at R8.main(R8.kt:1)",
-        "    at R8.main(R8.foo)",
+        "    at R8.main(R8.java)",
         "    at R8.main(R8.java)",
         "    at R8.main(R8.java)",
         "    at R8.main(R8.java)",
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineFileNameStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineFileNameStackTrace.java
index 65f9824..ebc23a1 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineFileNameStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineFileNameStackTrace.java
@@ -21,10 +21,10 @@
   public List<String> retracedStackTrace() {
     return Arrays.asList(
         "Exception in thread \"main\" java.lang.NullPointerException",
-        "\tat foo.Bar$Baz.baz(Bar.dummy:0)",
-        "\tat Foo$Bar.bar(Foo.dummy:2)",
-        "\tat com.android.tools.r8.naming.retrace.Main$Foo.method1(Main.dummy:8)",
-        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.dummy:7)");
+        "\tat foo.Bar$Baz.baz(Bar.java:0)",
+        "\tat Foo$Bar.bar(Foo.java:2)",
+        "\tat com.android.tools.r8.naming.retrace.Main$Foo.method1(Main.java:8)",
+        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java:7)");
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineFileNameWithInnerClassesStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineFileNameWithInnerClassesStackTrace.java
index 20ed6ec..8d7551a 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineFileNameWithInnerClassesStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineFileNameWithInnerClassesStackTrace.java
@@ -21,8 +21,8 @@
   public List<String> retracedStackTrace() {
     return Arrays.asList(
         "Exception in thread \"main\" java.lang.NullPointerException",
-        "\tat foo.Bar$Baz$Qux.baz(Bar.dummy:0)",
-        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.dummy:7)");
+        "\tat foo.Bar$Baz$Qux.baz(Bar.java:0)",
+        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java:7)");
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscationRangeMappingWithStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscationRangeMappingWithStackTrace.java
index 4057a91..3f944e2 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscationRangeMappingWithStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscationRangeMappingWithStackTrace.java
@@ -24,10 +24,10 @@
   public List<String> retracedStackTrace() {
     return Arrays.asList(
         "Exception in thread \"main\" java.lang.NullPointerException",
-        "\tat com.android.tools.r8.naming.retrace.Main.foo(Main.dummy:1)",
-        "\tat com.android.tools.r8.naming.retrace.Main.bar(Main.dummy:3)",
-        "\tat com.android.tools.r8.naming.retrace.Main.baz(Main.dummy:8)",
-        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.dummy:7)");
+        "\tat com.android.tools.r8.naming.retrace.Main.foo(Main.java:1)",
+        "\tat com.android.tools.r8.naming.retrace.Main.bar(Main.java:3)",
+        "\tat com.android.tools.r8.naming.retrace.Main.baz(Main.java:8)",
+        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java:7)");
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
index f81dffd..37f26db 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -94,6 +95,11 @@
   }
 
   @Override
+  public MethodAccessFlags getAccessFlags() {
+    throw new Unreachable("Cannot get the access flags for an absent method");
+  }
+
+  @Override
   public DexEncodedMethod getMethod() {
     return null;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index 4e6a3f5..3a9976f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -196,6 +196,11 @@
   }
 
   @Override
+  public boolean isIfEq() {
+    return instruction instanceof CfIf && ((CfIf) instruction).getOpcode() == Opcodes.IF_ICMPEQ;
+  }
+
+  @Override
   public boolean isIfEqz() {
     return instruction instanceof CfIf && ((CfIf) instruction).getOpcode() == Opcodes.IFEQ;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 26944f9..4b9f733 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -316,6 +316,11 @@
   }
 
   @Override
+  public boolean isIfEq() {
+    return instruction instanceof IfEq;
+  }
+
+  @Override
   public boolean isIfEqz() {
     return instruction instanceof IfEqz;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 4d5e0d3..c2ae3ab 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -26,6 +26,7 @@
 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.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.naming.MemberNaming;
@@ -142,6 +143,11 @@
   }
 
   @Override
+  public MethodAccessFlags getAccessFlags() {
+    return dexMethod.getAccessFlags();
+  }
+
+  @Override
   public DexEncodedMethod getMethod() {
     return dexMethod;
   }
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 e6e26fd..56436e9 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
@@ -75,6 +75,8 @@
 
   boolean isIfNez();
 
+  boolean isIfEq();
+
   boolean isIfEqz();
 
   boolean isReturn();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index c6a63e2..4826876 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -37,6 +38,8 @@
     return null;
   }
 
+  public abstract MethodAccessFlags getAccessFlags();
+
   @Override
   public abstract MethodSignature getOriginalSignature();
 
diff --git a/tests/r8_api_usage_sample.jar b/tests/r8_api_usage_sample.jar
index c50e110..65070f5 100644
--- a/tests/r8_api_usage_sample.jar
+++ b/tests/r8_api_usage_sample.jar
Binary files differ
diff --git a/tools/archive.py b/tools/archive.py
index 3bf5514..b7ca7c0 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -158,7 +158,7 @@
   is_master = IsMaster(version)
   if is_master:
     # On master we use the git hash to archive with
-    print 'On master, using git hash for archiving'
+    print('On master, using git hash for archiving')
     version = GetGitHash()
 
   destination = GetVersionDestination('gs://', version, is_master)
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 29fed2c..b5f49d1 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -106,7 +106,7 @@
   return parser
 
 def error(msg):
-  print msg
+  print(msg)
   sys.exit(1)
 
 class Dump(object):
@@ -145,11 +145,11 @@
 
   def proguard_input_map(self):
     if self.if_exists('proguard_input.config'):
-      print "Unimplemented: proguard_input configuration."
+      print("Unimplemented: proguard_input configuration.")
 
   def main_dex_resource(self):
     if self.if_exists('main-dex-list.txt'):
-      print "Unimplemented: main-dex-list."
+      print("Unimplemented: main-dex-list.")
 
   def build_properties_file(self):
     return self.if_exists('build.properties')
@@ -270,7 +270,7 @@
     if not dump.program_jar():
       error("Cannot compile dump with no program classes")
     if not dump.library_jar():
-      print "WARNING: Unexpected lack of library classes in dump"
+      print("WARNING: Unexpected lack of library classes in dump")
     build_properties = determine_build_properties(args, dump)
     version = determine_version(args, dump)
     compiler = determine_compiler(args, dump)
@@ -284,7 +284,7 @@
     cmd = [jdk.GetJavaExecutable()]
     if args.debug_agent:
       if not args.nolib:
-        print "WARNING: Running debugging agent on r8lib is questionable..."
+        print("WARNING: Running debugging agent on r8lib is questionable...")
       cmd.append(
           '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005')
     if args.xmx:
@@ -329,18 +329,18 @@
     cmd.extend(otherargs)
     utils.PrintCmd(cmd)
     try:
-      print subprocess.check_output(cmd, stderr=subprocess.STDOUT)
+      print(subprocess.check_output(cmd, stderr=subprocess.STDOUT))
       return 0
-    except subprocess.CalledProcessError, e:
-      print e.output
+    except subprocess.CalledProcessError as e:
+      print(e.output)
       if not args.nolib and version != 'source':
         stacktrace = os.path.join(temp, 'stacktrace')
         open(stacktrace, 'w+').write(e.output)
         local_map = utils.R8LIB_MAP if version == 'master' else None
         hash_or_version = None if version == 'master' else version
-        print "=" * 80
-        print " RETRACED OUTPUT"
-        print "=" * 80
+        print("=" * 80)
+        print(" RETRACED OUTPUT")
+        print("=" * 80)
         retrace.run(
           local_map, hash_or_version, stacktrace, is_hash(version), no_r8lib=False)
       return 1
diff --git a/tools/gradle.py b/tools/gradle.py
index a52e3f3..b6f5187 100755
--- a/tools/gradle.py
+++ b/tools/gradle.py
@@ -47,7 +47,7 @@
 def PrintCmd(s):
   if type(s) is list:
     s = ' '.join(s)
-  print 'Running: %s' % s
+  print('Running: %s' % s)
   # I know this will hit os on windows eventually if we don't do this.
   sys.stdout.flush()
 
diff --git a/tools/jdk.py b/tools/jdk.py
index bc40873..7138fda 100755
--- a/tools/jdk.py
+++ b/tools/jdk.py
@@ -40,7 +40,7 @@
   return os.path.join(jdkHome, 'bin', executable) if jdkHome else executable
 
 def Main():
-  print GetJdkHome()
+  print(GetJdkHome())
 
 if __name__ == '__main__':
   sys.exit(Main())
diff --git a/tools/bisect.py b/tools/r8bisect.py
similarity index 100%
rename from tools/bisect.py
rename to tools/r8bisect.py
diff --git a/tools/retrace.py b/tools/retrace.py
index 41b5a06..b0a1ec8 100755
--- a/tools/retrace.py
+++ b/tools/retrace.py
@@ -107,6 +107,9 @@
     map_path
   ]
 
+  if quiet:
+    retrace_args.append('--quiet')
+
   if stacktrace:
     retrace_args.append(stacktrace)
 
diff --git a/tools/run_on_app_dump.py b/tools/run_on_app_dump.py
index 1461181..4509656 100755
--- a/tools/run_on_app_dump.py
+++ b/tools/run_on_app_dump.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # 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.
@@ -221,15 +221,6 @@
     'revision': '94342117097770ea3ca2c6df6ab496a1a55c3ce7',
     'folder': 'rover-android',
   }),
-  App({
-    'id': 'io.rover.app.debug',
-    'name': 'Rover',
-    'dump_app': 'dump_app.zip',
-    'apk_app': 'example-app-release-unsigned.apk',
-    'url': 'https://github.com/RoverPlatform/rover-android',
-    'revision': '94342117097770ea3ca2c6df6ab496a1a55c3ce7',
-    'folder': 'rover-android',
-  }),
   # TODO(b/172808159): Monkey runner does not work
   App({
     'id': 'com.google.android.apps.santatracker',
diff --git a/tools/test.py b/tools/test.py
index cf14685..dffc4565 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -253,6 +253,7 @@
     # Force gradle to build a version of r8lib without dependencies for
     # BootstrapCurrentEqualityTest.
     gradle_args.append('R8LibNoDeps')
+    gradle_args.append('R8Retrace')
   if options.r8lib_no_deps:
     gradle_args.append('-Pr8lib_no_deps')
   if options.worktree:
diff --git a/tools/update_prebuilds_in_android.py b/tools/update_prebuilds_in_android.py
index 22c8863..fe8a3c0 100755
--- a/tools/update_prebuilds_in_android.py
+++ b/tools/update_prebuilds_in_android.py
@@ -53,14 +53,14 @@
     src = os.path.join(root, srcs[i])
     dest = os.path.join(target_root, 'prebuilts', 'r8', dests[i])
     if os.path.exists(dest):
-      print 'Copying: ' + src + ' -> ' + dest
+      print('Copying: ' + src + ' -> ' + dest)
       copyfile(src, dest)
       if maps:
-        print 'Copying: ' + src + '.map -> ' + dest + '.map'
+        print('Copying: ' + src + '.map -> ' + dest + '.map')
         copyfile(src + '.map', dest + '.map')
     else:
-      print ('WARNING: Not copying ' + src + ' -> ' + dest +
-             ', as' + dest + ' does not exist already')
+      print('WARNING: Not copying ' + src + ' -> ' + dest +
+            ', as' + dest + ' does not exist already')
 
 def copy_jar_targets(root, target_root, jar_targets, maps):
   srcs = map((lambda t: t[0] + '.jar'), jar_targets)
@@ -83,7 +83,7 @@
     target,
     is_hash)
   if not quiet:
-    print 'Downloading: ' + url + ' -> ' + download_path
+    print('Downloading: ' + url + ' -> ' + download_path)
   utils.download_file_from_cloud_storage(url, download_path, quiet=quiet)
 
 def main_download(hash, maps, targets, target_root, version):
diff --git a/tools/utils.py b/tools/utils.py
index bed28d8..351a038 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -222,7 +222,7 @@
     # there is an update.
     os.utime(tgz, None)
   else:
-    print 'Ensure cloud dependency:', msg, 'present'
+    print('Ensure cloud dependency:', msg, 'present')
 
 def DownloadFromX20(sha1_file):
   download_script = os.path.join(REPO_ROOT, 'tools', 'download_from_x20.py')
@@ -336,7 +336,7 @@
 def unpack_archive(filename):
   dest_dir = extract_dir(filename)
   if os.path.exists(dest_dir):
-    print 'Deleting existing dir %s' % dest_dir
+    print('Deleting existing dir %s' % dest_dir)
     shutil.rmtree(dest_dir)
   dirname = os.path.dirname(os.path.abspath(filename))
   with tarfile.open(filename, 'r:gz') as tar:
@@ -376,12 +376,12 @@
  def __enter__(self):
    self._old_cwd = os.getcwd()
    if not self._quiet:
-     print 'Enter directory:', self._working_directory
+     print('Enter directory:', self._working_directory)
    os.chdir(self._working_directory)
 
  def __exit__(self, *_):
    if not self._quiet:
-     print 'Enter directory:', self._old_cwd
+     print('Enter directory:', self._old_cwd)
    os.chdir(self._old_cwd)
 
 # Reading Android CTS test_result.xml