Merge commit '71a482b7a8ed665c53b1a0eb188f218c86213984' into dev-release
diff --git a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
index d412e52..52d878b 100644
--- a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
+++ b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
@@ -21,6 +21,9 @@
           "j$/util/stream/Collector$Characteristics");
   private static final Set<String> WRAP_CONVERT_OWNER =
       ImmutableSet.of(
+          "j$/util/stream/DoubleStream",
+          "j$/util/stream/IntStream",
+          "j$/util/stream/LongStream",
           "j$/util/stream/Stream",
           "j$/nio/file/spi/FileSystemProvider",
           "j$/nio/file/spi/FileTypeDetector",
diff --git a/src/library_desugar/java/j$/util/stream/DoubleStream.java b/src/library_desugar/java/j$/util/stream/DoubleStream.java
new file mode 100644
index 0000000..1fdab0d
--- /dev/null
+++ b/src/library_desugar/java/j$/util/stream/DoubleStream.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2022, 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 j$.util.stream;
+
+public class DoubleStream {
+
+  public static java.util.stream.DoubleStream wrap_convert(j$.util.stream.DoubleStream stream) {
+    return null;
+  }
+
+  public static j$.util.stream.DoubleStream wrap_convert(java.util.stream.DoubleStream stream) {
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/j$/util/stream/IntStream.java b/src/library_desugar/java/j$/util/stream/IntStream.java
new file mode 100644
index 0000000..75165a8
--- /dev/null
+++ b/src/library_desugar/java/j$/util/stream/IntStream.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2022, 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 j$.util.stream;
+
+public class IntStream {
+
+  public static java.util.stream.IntStream wrap_convert(j$.util.stream.IntStream stream) {
+    return null;
+  }
+
+  public static j$.util.stream.IntStream wrap_convert(java.util.stream.IntStream stream) {
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/j$/util/stream/LongStream.java b/src/library_desugar/java/j$/util/stream/LongStream.java
new file mode 100644
index 0000000..d078146
--- /dev/null
+++ b/src/library_desugar/java/j$/util/stream/LongStream.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2022, 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 j$.util.stream;
+
+public class LongStream {
+
+  public static java.util.stream.LongStream wrap_convert(j$.util.stream.LongStream stream) {
+    return null;
+  }
+
+  public static j$.util.stream.LongStream wrap_convert(java.util.stream.LongStream stream) {
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/j$/util/stream/Stream.java b/src/library_desugar/java/j$/util/stream/Stream.java
index 70c761a..dc5861d 100644
--- a/src/library_desugar/java/j$/util/stream/Stream.java
+++ b/src/library_desugar/java/j$/util/stream/Stream.java
@@ -13,4 +13,12 @@
   public static j$.util.stream.Stream<?> inverted_wrap_convert(java.util.stream.Stream<?> stream) {
     return null;
   }
+
+  public static java.util.stream.Stream<?> wrap_convert(j$.util.stream.Stream<?> stream) {
+    return null;
+  }
+
+  public static j$.util.stream.Stream<?> wrap_convert(java.util.stream.Stream<?> stream) {
+    return null;
+  }
 }
diff --git a/src/library_desugar/java/java/util/stream/FlatMapApiFlips.java b/src/library_desugar/java/java/util/stream/FlatMapApiFlips.java
new file mode 100644
index 0000000..b9e1b9e
--- /dev/null
+++ b/src/library_desugar/java/java/util/stream/FlatMapApiFlips.java
@@ -0,0 +1,164 @@
+// Copyright (c) 2022, 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 java.util.stream;
+
+import static java.util.ConversionRuntimeException.exception;
+
+import java.util.function.DoubleFunction;
+import java.util.function.Function;
+import java.util.function.IntFunction;
+import java.util.function.LongFunction;
+
+public class FlatMapApiFlips {
+
+  public static Function<?, ?> flipFunctionReturningStream(Function<?, ?> function) {
+    return new FunctionStreamWrapper<>(function);
+  }
+
+  public static IntFunction<?> flipFunctionReturningStream(IntFunction<?> function) {
+    return new IntFunctionStreamWrapper<>(function);
+  }
+
+  public static DoubleFunction<?> flipFunctionReturningStream(DoubleFunction<?> function) {
+    return new DoubleFunctionStreamWrapper<>(function);
+  }
+
+  public static LongFunction<?> flipFunctionReturningStream(LongFunction<?> function) {
+    return new LongFunctionStreamWrapper<>(function);
+  }
+
+  public static class FunctionStreamWrapper<T, R> implements Function<T, R> {
+
+    public Function<T, R> function;
+
+    public FunctionStreamWrapper(Function<T, R> function) {
+      this.function = function;
+    }
+
+    private R flipStream(R maybeStream) {
+      if (maybeStream == null) {
+        return null;
+      }
+
+      if (maybeStream instanceof java.util.stream.Stream<?>) {
+        return (R) j$.util.stream.Stream.wrap_convert((java.util.stream.Stream<?>) maybeStream);
+      }
+      if (maybeStream instanceof j$.util.stream.Stream<?>) {
+        return (R) j$.util.stream.Stream.wrap_convert((j$.util.stream.Stream<?>) maybeStream);
+      }
+
+      if (maybeStream instanceof java.util.stream.IntStream) {
+        return (R) j$.util.stream.IntStream.wrap_convert((java.util.stream.IntStream) maybeStream);
+      }
+      if (maybeStream instanceof j$.util.stream.IntStream) {
+        return (R) j$.util.stream.IntStream.wrap_convert((j$.util.stream.IntStream) maybeStream);
+      }
+
+      if (maybeStream instanceof java.util.stream.DoubleStream) {
+        return (R)
+            j$.util.stream.DoubleStream.wrap_convert((java.util.stream.DoubleStream) maybeStream);
+      }
+      if (maybeStream instanceof j$.util.stream.DoubleStream) {
+        return (R)
+            j$.util.stream.DoubleStream.wrap_convert((j$.util.stream.DoubleStream) maybeStream);
+      }
+
+      if (maybeStream instanceof java.util.stream.LongStream) {
+        return (R)
+            j$.util.stream.LongStream.wrap_convert((java.util.stream.LongStream) maybeStream);
+      }
+      if (maybeStream instanceof j$.util.stream.LongStream) {
+        return (R) j$.util.stream.LongStream.wrap_convert((j$.util.stream.LongStream) maybeStream);
+      }
+
+      throw exception("java.util.stream.*Stream", maybeStream.getClass());
+    }
+
+    public R apply(T arg) {
+      return flipStream(function.apply(arg));
+    }
+  }
+
+  public static class IntFunctionStreamWrapper<R> implements IntFunction<R> {
+
+    public IntFunction<R> function;
+
+    public IntFunctionStreamWrapper(IntFunction<R> function) {
+      this.function = function;
+    }
+
+    private R flipStream(R maybeStream) {
+      if (maybeStream == null) {
+        return null;
+      }
+      if (maybeStream instanceof java.util.stream.IntStream) {
+        return (R) j$.util.stream.IntStream.wrap_convert((java.util.stream.IntStream) maybeStream);
+      }
+      if (maybeStream instanceof j$.util.stream.IntStream) {
+        return (R) j$.util.stream.IntStream.wrap_convert((j$.util.stream.IntStream) maybeStream);
+      }
+      throw exception("java.util.stream.IntStream", maybeStream.getClass());
+    }
+
+    public R apply(int arg) {
+      return flipStream(function.apply(arg));
+    }
+  }
+
+  public static class DoubleFunctionStreamWrapper<R> implements DoubleFunction<R> {
+
+    public DoubleFunction<R> function;
+
+    public DoubleFunctionStreamWrapper(DoubleFunction<R> function) {
+      this.function = function;
+    }
+
+    private R flipStream(R maybeStream) {
+      if (maybeStream == null) {
+        return null;
+      }
+      if (maybeStream instanceof java.util.stream.DoubleStream) {
+        return (R)
+            j$.util.stream.DoubleStream.wrap_convert((java.util.stream.DoubleStream) maybeStream);
+      }
+      if (maybeStream instanceof j$.util.stream.DoubleStream) {
+        return (R)
+            j$.util.stream.DoubleStream.wrap_convert((j$.util.stream.DoubleStream) maybeStream);
+      }
+      throw exception("java.util.stream.DoubleStream", maybeStream.getClass());
+    }
+
+    public R apply(double arg) {
+      return flipStream(function.apply(arg));
+    }
+  }
+
+  public static class LongFunctionStreamWrapper<R> implements LongFunction<R> {
+
+    public LongFunction<R> function;
+
+    public LongFunctionStreamWrapper(LongFunction<R> function) {
+      this.function = function;
+    }
+
+    private R flipStream(R maybeStream) {
+      if (maybeStream == null) {
+        return null;
+      }
+      if (maybeStream instanceof java.util.stream.LongStream) {
+        return (R)
+            j$.util.stream.LongStream.wrap_convert((java.util.stream.LongStream) maybeStream);
+      }
+      if (maybeStream instanceof j$.util.stream.LongStream) {
+        return (R) j$.util.stream.LongStream.wrap_convert((j$.util.stream.LongStream) maybeStream);
+      }
+      throw exception("java.util.stream.LongStream", maybeStream.getClass());
+    }
+
+    public R apply(long arg) {
+      return flipStream(function.apply(arg));
+    }
+  }
+}
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs.json b/src/library_desugar/jdk11/desugar_jdk_libs.json
index 103035f..609f920 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs.json
@@ -100,6 +100,13 @@
       },
       "api_generic_types_conversion": {
         "java.util.Set java.util.stream.Collector#characteristics()" : [-1, "java.util.Set java.util.stream.StreamApiFlips#flipCharacteristicSet(java.util.Set)"],
+        "java.util.stream.Stream java.util.stream.Stream#flatMap(java.util.function.Function)": [0, "java.util.function.Function java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.Function)"],
+        "java.util.stream.DoubleStream java.util.stream.DoubleStream#flatMap(java.util.function.DoubleFunction)": [0, "java.util.function.DoubleFunction java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.DoubleFunction)"],
+        "java.util.stream.DoubleStream java.util.stream.Stream#flatMapToDouble(java.util.function.Function)": [0, "java.util.function.Function java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.Function)"],
+        "java.util.stream.IntStream java.util.stream.Stream#flatMapToInt(java.util.function.Function)": [0, "java.util.function.Function java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.Function)"],
+        "java.util.stream.IntStream java.util.stream.IntStream#flatMap(java.util.function.IntFunction)": [0, "java.util.function.IntFunction java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.IntFunction)"],
+        "java.util.stream.LongStream java.util.stream.Stream#flatMapToLong(java.util.function.Function)": [0, "java.util.function.Function java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.Function)"],
+        "java.util.stream.LongStream java.util.stream.LongStream#flatMap(java.util.function.LongFunction)": [0, "java.util.function.LongFunction java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.LongFunction)"],
         "java.lang.Object java.lang.StackWalker#walk(java.util.function.Function)": [0, "java.util.function.Function java.util.stream.StackWalkerApiFlips#flipFunctionStream(java.util.function.Function)"]
       },
       "never_outline_api": [
@@ -234,6 +241,15 @@
         "java.util.Optional": {
           "j$.util.Optional": "java.util.Optional"
         },
+        "java.util.stream.DoubleStream": {
+          "j$.util.stream.DoubleStream": "java.util.stream.DoubleStream"
+        },
+        "java.util.stream.IntStream": {
+          "j$.util.stream.IntStream": "java.util.stream.IntStream"
+        },
+        "java.util.stream.LongStream": {
+          "j$.util.stream.LongStream": "java.util.stream.LongStream"
+        },
         "java.util.stream.Stream": {
           "j$.util.stream.Stream": "java.util.stream.Stream"
         }
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index a53fd3b..e2f74e8 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -237,6 +237,13 @@
       },
       "api_generic_types_conversion": {
         "java.util.Set java.util.stream.Collector#characteristics()" : [-1, "java.util.Set java.util.stream.StreamApiFlips#flipCharacteristicSet(java.util.Set)"],
+        "java.util.stream.Stream java.util.stream.Stream#flatMap(java.util.function.Function)": [0, "java.util.function.Function java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.Function)"],
+        "java.util.stream.DoubleStream java.util.stream.DoubleStream#flatMap(java.util.function.DoubleFunction)": [0, "java.util.function.DoubleFunction java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.DoubleFunction)"],
+        "java.util.stream.DoubleStream java.util.stream.Stream#flatMapToDouble(java.util.function.Function)": [0, "java.util.function.Function java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.Function)"],
+        "java.util.stream.IntStream java.util.stream.Stream#flatMapToInt(java.util.function.Function)": [0, "java.util.function.Function java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.Function)"],
+        "java.util.stream.IntStream java.util.stream.IntStream#flatMap(java.util.function.IntFunction)": [0, "java.util.function.IntFunction java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.IntFunction)"],
+        "java.util.stream.LongStream java.util.stream.Stream#flatMapToLong(java.util.function.Function)": [0, "java.util.function.Function java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.Function)"],
+        "java.util.stream.LongStream java.util.stream.LongStream#flatMap(java.util.function.LongFunction)": [0, "java.util.function.LongFunction java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.LongFunction)"],
         "java.lang.Object java.lang.StackWalker#walk(java.util.function.Function)": [0, "java.util.function.Function java.util.stream.StackWalkerApiFlips#flipFunctionStream(java.util.function.Function)"]
       },
       "never_outline_api": [
@@ -454,6 +461,15 @@
         "java.util.Optional": {
           "j$.util.Optional": "java.util.Optional"
         },
+        "java.util.stream.DoubleStream": {
+          "j$.util.stream.DoubleStream": "java.util.stream.DoubleStream"
+        },
+        "java.util.stream.IntStream": {
+          "j$.util.stream.IntStream": "java.util.stream.IntStream"
+        },
+        "java.util.stream.LongStream": {
+          "j$.util.stream.LongStream": "java.util.stream.LongStream"
+        },
         "java.util.stream.Stream": {
           "j$.util.stream.Stream": "java.util.stream.Stream"
         }
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 02e06d1..a233e49 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -12,6 +12,9 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.profile.art.ArtProfileConsumer;
+import com.android.tools.r8.profile.art.ArtProfileForRewriting;
+import com.android.tools.r8.profile.art.ArtProfileProvider;
 import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
@@ -57,6 +60,7 @@
   private final MapIdProvider mapIdProvider;
   private final SourceFileProvider sourceFileProvider;
   private final boolean isAndroidPlatformBuild;
+  private final List<ArtProfileForRewriting> artProfilesForRewriting;
   private final List<StartupProfileProvider> startupProfileProviders;
   private final ClassConflictResolver classConflictResolver;
 
@@ -78,6 +82,7 @@
     mapIdProvider = null;
     sourceFileProvider = null;
     isAndroidPlatformBuild = false;
+    artProfilesForRewriting = null;
     startupProfileProviders = null;
     classConflictResolver = null;
   }
@@ -100,6 +105,7 @@
       MapIdProvider mapIdProvider,
       SourceFileProvider sourceFileProvider,
       boolean isAndroidPlatformBuild,
+      List<ArtProfileForRewriting> artProfilesForRewriting,
       List<StartupProfileProvider> startupProfileProviders,
       ClassConflictResolver classConflictResolver) {
     super(app);
@@ -121,6 +127,7 @@
     this.mapIdProvider = mapIdProvider;
     this.sourceFileProvider = sourceFileProvider;
     this.isAndroidPlatformBuild = isAndroidPlatformBuild;
+    this.artProfilesForRewriting = artProfilesForRewriting;
     this.startupProfileProviders = startupProfileProviders;
     this.classConflictResolver = classConflictResolver;
   }
@@ -219,6 +226,10 @@
     return isAndroidPlatformBuild;
   }
 
+  List<ArtProfileForRewriting> getArtProfilesForRewriting() {
+    return artProfilesForRewriting;
+  }
+
   List<StartupProfileProvider> getStartupProfileProviders() {
     return startupProfileProviders;
   }
@@ -258,7 +269,6 @@
     protected DesugarState desugarState = DesugarState.ON;
     private List<StringResource> desugaredLibrarySpecificationResources = new ArrayList<>();
     private boolean includeClassesChecksum = false;
-    private boolean lookupLibraryBeforeProgram = true;
     private boolean optimizeMultidexForLinearAlloc = false;
     private BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
     private List<AssertionsConfiguration> assertionsConfiguration = new ArrayList<>();
@@ -268,6 +278,7 @@
     private MapIdProvider mapIdProvider = null;
     private SourceFileProvider sourceFileProvider = null;
     private boolean isAndroidPlatformBuild = false;
+    private List<ArtProfileForRewriting> artProfilesForRewriting = new ArrayList<>();
     private List<StartupProfileProvider> startupProfileProviders = new ArrayList<>();
     private ClassConflictResolver classConflictResolver = null;
 
@@ -685,6 +696,23 @@
       return isAndroidPlatformBuild;
     }
 
+    /**
+     * Add an ART profiles that should be rewritten to match the residual application. The ART
+     * profile is given to the compiler by the {@link ArtProfileProvider} and passed back to the
+     * {@link ArtProfileConsumer} at the end of compilation when the ART profile has been fully
+     * rewritten to match the residual application.
+     */
+    public B addArtProfileForRewriting(
+        ArtProfileProvider artProfileProvider, ArtProfileConsumer residualArtProfileProvider) {
+      artProfilesForRewriting.add(
+          new ArtProfileForRewriting(artProfileProvider, residualArtProfileProvider));
+      return self();
+    }
+
+    List<ArtProfileForRewriting> getArtProfilesForRewriting() {
+      return artProfilesForRewriting;
+    }
+
     B addStartupProfileProviders(StartupProfileProvider... startupProfileProviders) {
       return addStartupProfileProviders(Arrays.asList(startupProfileProviders));
     }
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 8be1456..74a3d20 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -46,19 +46,16 @@
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.ExceptionUtils;
 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;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -278,15 +275,6 @@
       boolean hasDexResources = appView.appInfo().app().getFlags().hasReadProgramClassFromDex();
 
       Marker marker = options.getMarker(Tool.D8);
-      Set<Marker> markers = new HashSet<>(appView.dexItemFactory().extractMarkers());
-      // TODO(b/166617364): Don't add an additional marker when desugaring is turned off.
-      if (hasClassResources
-          && (options.desugarState != DesugarState.OFF
-              || markers.isEmpty()
-              || (markers.size() == 1 && markers.iterator().next().isL8()))) {
-        markers.add(marker);
-      }
-      Marker.checkCompatibleDesugaredLibrary(markers, options.reporter);
 
       timing.time(
           "Run inspections",
@@ -314,7 +302,7 @@
         // without iterating again the IR. We fall-back to writing one app with rewriting and
         // merging it with the other app in rewriteNonDexInputs.
         timing.begin("Rewrite non-dex inputs");
-        DexApplication app = rewriteNonDexInputs(appView, inputApp, executor, timing);
+        DexApplication app = rewriteNonDexInputs(appView, inputApp, executor, marker, timing);
         timing.end();
         appView.setAppInfo(
             new AppInfo(
@@ -345,8 +333,7 @@
       if (options.isGeneratingClassFiles()) {
         new CfApplicationWriter(appView, marker).write(options.getClassFileConsumer(), inputApp);
       } else {
-        new ApplicationWriter(appView, marker == null ? null : ImmutableList.copyOf(markers))
-            .write(executor, inputApp);
+        new ApplicationWriter(appView, marker).write(executor, inputApp);
       }
       options.printWarnings();
     } catch (ExecutionException e) {
@@ -408,7 +395,11 @@
   }
 
   private static DexApplication rewriteNonDexInputs(
-      AppView<AppInfo> appView, AndroidApp inputApp, ExecutorService executor, Timing timing)
+      AppView<AppInfo> appView,
+      AndroidApp inputApp,
+      ExecutorService executor,
+      Marker marker,
+      Timing timing)
       throws IOException, ExecutionException {
     // TODO(b/154575955): Remove the naming lens in D8.
     appView
@@ -437,11 +428,7 @@
     ConvertedCfFiles convertedCfFiles = new ConvertedCfFiles();
     new GenericSignatureRewriter(appView).run(appView.appInfo().classes(), executor);
     new KotlinMetadataRewriter(appView).runForD8(executor);
-    new ApplicationWriter(
-            appView,
-            null,
-            convertedCfFiles)
-        .write(executor);
+    new ApplicationWriter(appView, marker, convertedCfFiles).write(executor);
     AndroidApp.Builder builder = AndroidApp.builder(inputApp);
     builder.getProgramResourceProviders().clear();
     builder.addProgramResourceProvider(convertedCfFiles);
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 4eba3b9..e7f27d5 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.profile.art.ArtProfileForRewriting;
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.shaking.ProguardConfigurationSource;
@@ -457,6 +458,7 @@
           proguardMapConsumer,
           enableMissingLibraryApiModeling,
           getAndroidPlatformBuild(),
+          getArtProfilesForRewriting(),
           getStartupProfileProviders(),
           getClassConflictResolver(),
           factory);
@@ -549,6 +551,7 @@
       StringConsumer proguardMapConsumer,
       boolean enableMissingLibraryApiModeling,
       boolean isAndroidPlatformBuild,
+      List<ArtProfileForRewriting> artProfilesForRewriting,
       List<StartupProfileProvider> startupProfileProviders,
       ClassConflictResolver classConflictResolver,
       DexItemFactory factory) {
@@ -570,6 +573,7 @@
         mapIdProvider,
         null,
         isAndroidPlatformBuild,
+        artProfilesForRewriting,
         startupProfileProviders,
         classConflictResolver);
     this.intermediate = intermediate;
@@ -660,7 +664,7 @@
 
     if (!enableMissingLibraryApiModeling) {
       internal.apiModelingOptions().disableApiCallerIdentification();
-      internal.apiModelingOptions().disableMissingApiModeling();
+      internal.apiModelingOptions().disableOutliningAndStubbing();
     }
 
     // Default is to remove all javac generated assertion code when generating dex.
@@ -694,6 +698,10 @@
 
     internal.configureAndroidPlatformBuild(getAndroidPlatformBuild());
 
+    internal
+        .getArtProfileOptions()
+        .setArtProfilesForRewriting(getArtProfilesForRewriting())
+        .setPassthrough(true);
     internal.getStartupOptions().setStartupProfileProviders(getStartupProfileProviders());
 
     internal.programClassConflictResolver =
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 7a2950d..94440be 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.inspector.Inspector;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.profile.art.ArtProfileForRewriting;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AssertionConfigurationWithDefault;
@@ -121,6 +122,7 @@
         null,
         false,
         null,
+        null,
         classConflictResolver);
     this.d8Command = d8Command;
     this.r8Command = r8Command;
@@ -223,7 +225,7 @@
     // Disable global optimizations.
     internal.disableGlobalOptimizations();
     internal.apiModelingOptions().disableApiCallerIdentification();
-    internal.apiModelingOptions().disableMissingApiModeling();
+    internal.apiModelingOptions().disableOutliningAndStubbing();
 
     internal.setDumpInputFlags(getDumpInputFlags());
     internal.dumpOptions = dumpOptions();
@@ -372,6 +374,11 @@
                 .setIncludeClassesChecksum(getIncludeClassesChecksum())
                 .setDexClassChecksumFilter(getDexClassChecksumFilter())
                 .setProgramConsumer(getProgramConsumer());
+        for (ArtProfileForRewriting artProfileForRewriting : getArtProfilesForRewriting()) {
+          r8Builder.addArtProfileForRewriting(
+              artProfileForRewriting.getArtProfileProvider(),
+              artProfileForRewriting.getResidualArtProfileConsumer());
+        }
         for (ClassFileResourceProvider libraryResourceProvider :
             inputs.getLibraryResourceProviders()) {
           r8Builder.addLibraryResourceProvider(libraryResourceProvider);
@@ -403,6 +410,11 @@
                 .setIncludeClassesChecksum(getIncludeClassesChecksum())
                 .setDexClassChecksumFilter(getDexClassChecksumFilter())
                 .setProgramConsumer(getProgramConsumer());
+        for (ArtProfileForRewriting artProfileForRewriting : getArtProfilesForRewriting()) {
+          d8Builder.addArtProfileForRewriting(
+              artProfileForRewriting.getArtProfileProvider(),
+              artProfileForRewriting.getResidualArtProfileConsumer());
+        }
         for (ClassFileResourceProvider libraryResourceProvider :
             inputs.getLibraryResourceProviders()) {
           d8Builder.addLibraryResourceProvider(libraryResourceProvider);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 5855643..d4e7f3a 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -69,7 +69,7 @@
 import com.android.tools.r8.optimize.AccessModifier;
 import com.android.tools.r8.optimize.MemberRebindingAnalysis;
 import com.android.tools.r8.optimize.MemberRebindingIdentityLensFactory;
-import com.android.tools.r8.optimize.VisibilityBridgeRemover;
+import com.android.tools.r8.optimize.RedundantBridgeRemover;
 import com.android.tools.r8.optimize.bridgehoisting.BridgeHoisting;
 import com.android.tools.r8.optimize.interfaces.analysis.CfOpenClosedInterfacesAnalysis;
 import com.android.tools.r8.optimize.proto.ProtoNormalizer;
@@ -112,7 +112,6 @@
 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;
 import com.google.common.collect.Iterables;
 import com.google.common.io.ByteStreams;
 import java.io.ByteArrayOutputStream;
@@ -123,7 +122,6 @@
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -215,18 +213,10 @@
     try {
       Marker marker = options.getMarker(Tool.R8);
       assert marker != null;
-      // Get the markers from the input which are different from the one created for this
-      // compilation
-      Set<Marker> markers = new HashSet<>(appView.dexItemFactory().extractMarkers());
-      markers.remove(marker);
       if (options.isGeneratingClassFiles()) {
         new CfApplicationWriter(appView, marker).write(options.getClassFileConsumer(), inputApp);
       } else {
-        new ApplicationWriter(
-                appView,
-                // Ensure that the marker for this compilation is the first in the list.
-                ImmutableList.<Marker>builder().add(marker).addAll(markers).build())
-            .write(executorService, inputApp);
+        new ApplicationWriter(appView, marker).write(executorService, inputApp);
       }
     } catch (IOException e) {
       throw new RuntimeException("Cannot write application", e);
@@ -467,10 +457,10 @@
                 subtypingInfo);
         boolean changed = appView.setGraphLens(publicizedLens);
         if (changed) {
-          // We can now remove visibility bridges. Note that we do not need to update the
+          // We can now remove redundant bridges. Note that we do not need to update the
           // invoke-targets here, as the existing invokes will simply dispatch to the now
           // visible super-method. MemberRebinding, if run, will then dispatch it correctly.
-          new VisibilityBridgeRemover(appView.withLiveness()).run(executorService);
+          new RedundantBridgeRemover(appView.withLiveness()).run(executorService);
         }
       }
 
@@ -698,10 +688,10 @@
         }
       }
 
-      // Remove unneeded visibility bridges that have been inserted for member rebinding.
+      // Remove redundant bridges that have been inserted for member rebinding.
       // This can only be done if we have AppInfoWithLiveness.
       if (appView.appInfo().hasLiveness()) {
-        new VisibilityBridgeRemover(appView.withLiveness()).run(executorService);
+        new RedundantBridgeRemover(appView.withLiveness()).run(executorService);
       } else {
         // If we don't have AppInfoWithLiveness here, it must be because we are not shrinking. When
         // we are not shrinking, we can't move visibility bridges. In principle, though, it would be
@@ -853,19 +843,16 @@
 
   private static boolean allReferencesAssignedApiLevel(
       AppView<? extends AppInfoWithClassHierarchy> appView) {
-    if (!appView.options().apiModelingOptions().checkAllApiReferencesAreSet
+    if (!appView.options().apiModelingOptions().isCheckAllApiReferencesAreSet()
         || appView.options().configurationDebugging) {
       return true;
     }
-    // This will return false if we find anything in the library which is not modeled.
+    // This will assert false if we find anything in the library which is not modeled.
     for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
       clazz.forEachProgramMember(
           member -> {
             assert !member.getDefinition().getApiLevel().isNotSetApiLevel()
                 : "Every member should have been analyzed";
-            assert appView.options().apiModelingOptions().enableApiCallerIdentification
-                    || member.getDefinition().getApiLevel().isUnknownApiLevel()
-                : "Every member should have level UNKNOWN";
           });
     }
     return true;
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index cef341c..81efb90 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.naming.SourceFileRewriter;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.profile.art.ArtProfileForRewriting;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
 import com.android.tools.r8.shaking.ProguardConfigurationParserOptions;
@@ -662,6 +663,7 @@
               getSourceFileProvider(),
               enableMissingLibraryApiModeling,
               getAndroidPlatformBuild(),
+              getArtProfilesForRewriting(),
               getStartupProfileProviders(),
               getClassConflictResolver());
 
@@ -850,6 +852,7 @@
       SourceFileProvider sourceFileProvider,
       boolean enableMissingLibraryApiModeling,
       boolean isAndroidPlatformBuild,
+      List<ArtProfileForRewriting> artProfilesForRewriting,
       List<StartupProfileProvider> startupProfileProviders,
       ClassConflictResolver classConflictResolver) {
     super(
@@ -870,6 +873,7 @@
         mapIdProvider,
         sourceFileProvider,
         isAndroidPlatformBuild,
+        artProfilesForRewriting,
         startupProfileProviders,
         classConflictResolver);
     assert proguardConfiguration != null;
@@ -1018,7 +1022,8 @@
     internal.outputInspections = InspectorImpl.wrapInspections(getOutputInspections());
 
     if (!enableMissingLibraryApiModeling) {
-      internal.apiModelingOptions().disableMissingApiModeling();
+      internal.apiModelingOptions().disableApiCallerIdentification();
+      internal.apiModelingOptions().disableOutliningAndStubbing();
     }
 
     // Default is to remove all javac generated assertion code when generating dex.
@@ -1054,11 +1059,11 @@
         synthesizedClassPrefix.isEmpty()
             ? System.getProperty("com.android.tools.r8.synthesizedClassPrefix", "")
             : synthesizedClassPrefix;
-    boolean l8Shrinking = !synthesizedClassPrefix.isEmpty();
+    boolean l8Shrinking = !internal.synthesizedClassPrefix.isEmpty();
     // TODO(b/214382176): Enable all the time.
     internal.loadAllClassDefinitions = l8Shrinking;
     if (l8Shrinking) {
-      internal.apiModelingOptions().disableSubbingOfClasses();
+      internal.apiModelingOptions().disableStubbingOfClasses();
     }
     internal.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
 
@@ -1071,6 +1076,7 @@
 
     internal.configureAndroidPlatformBuild(getAndroidPlatformBuild());
 
+    internal.getArtProfileOptions().setArtProfilesForRewriting(getArtProfilesForRewriting());
     internal.getStartupOptions().setStartupProfileProviders(getStartupProfileProviders());
 
     internal.programClassConflictResolver =
diff --git a/src/main/java/com/android/tools/r8/TextOutputStream.java b/src/main/java/com/android/tools/r8/TextOutputStream.java
new file mode 100644
index 0000000..224d9da
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/TextOutputStream.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8;
+
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+
+@Keep
+public interface TextOutputStream {
+
+  OutputStream getOutputStream();
+
+  Charset getCharset();
+}
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java
index eaf3a3d..1c61851 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java
@@ -42,13 +42,15 @@
   public abstract ComputedApiLevel computeApiLevelForDefinition(
       Iterable<DexType> types, ComputedApiLevel unknownValue);
 
+  public abstract boolean isEnabled();
+
   public ComputedApiLevel computeApiLevelForDefinition(
       DexMember<?, ?> reference, DexItemFactory factory, ComputedApiLevel unknownValue) {
     return computeApiLevelForDefinition(reference.getReferencedBaseTypes(factory), unknownValue);
   }
 
   public static AndroidApiLevelCompute create(AppView<?> appView) {
-    return appView.options().apiModelingOptions().enableApiCallerIdentification
+    return appView.options().apiModelingOptions().enableLibraryApiModeling
         ? new DefaultAndroidApiLevelCompute(appView)
         : noAndroidApiLevelCompute();
   }
@@ -79,18 +81,23 @@
     @Override
     public ComputedApiLevel computeApiLevelForDefinition(
         Iterable<DexType> types, ComputedApiLevel unknownValue) {
-      return unknownValue;
+      return ComputedApiLevel.notSet();
+    }
+
+    @Override
+    public boolean isEnabled() {
+      return false;
     }
 
     @Override
     public ComputedApiLevel computeApiLevelForLibraryReference(
         DexReference reference, ComputedApiLevel unknownValue) {
-      return unknownValue;
+      return ComputedApiLevel.notSet();
     }
 
     @Override
     public ComputedApiLevel computeInitialMinApiLevel(InternalOptions options) {
-      return ComputedApiLevel.unknown();
+      return ComputedApiLevel.notSet();
     }
   }
 
@@ -115,6 +122,11 @@
     }
 
     @Override
+    public boolean isEnabled() {
+      return true;
+    }
+
+    @Override
     public ComputedApiLevel computeApiLevelForLibraryReference(
         DexReference reference, ComputedApiLevel unknownValue) {
       return cache.lookup(reference, unknownValue);
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
index 50855bb..c4d2d5a 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
@@ -36,7 +36,7 @@
 
   public static AndroidApiReferenceLevelCache create(
       AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
-    assert appView.options().apiModelingOptions().enableApiCallerIdentification;
+    assert appView.options().apiModelingOptions().isApiLibraryModelingEnabled();
     ImmutableList.Builder<AndroidApiForHashingReference> builder = ImmutableList.builder();
     BiConsumer<DexReference, AndroidApiLevel> addItemToList =
         ConsumerUtils.andThen(AndroidApiForHashingReference::create, builder::add);
diff --git a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
index 90fc9ee..db785cc 100644
--- a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -234,16 +235,15 @@
         if (!instruction.isArgument()) {
           break;
         }
+        DexMethod reference = code.context().getReference();
         TypeInfo argumentType;
         if (argumentIndex < 0) {
           argumentType =
-              code.method().isInstanceInitializer()
-                  ? new ThisInstanceInfo(instruction.asArgument(), code.method().getHolderType())
-                  : createInitializedType(code.method().getHolderType());
+              reference.isInstanceInitializerInlineIntoOrMerged(appView)
+                  ? new ThisInstanceInfo(instruction.asArgument(), reference.getHolderType())
+                  : createInitializedType(reference.getHolderType());
         } else {
-          argumentType =
-              createInitializedType(
-                  code.method().getReference().proto.parameters.values[argumentIndex]);
+          argumentType = createInitializedType(reference.proto.parameters.values[argumentIndex]);
         }
         Value outValue = instruction.outValue();
         if (outValue.outType().isObject()) {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index dc25d98..eab78b9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -260,8 +260,18 @@
   @Override
   void internalRegisterUse(
       UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
-    locals.values().forEach(frameType -> internalRegisterUse(registry, frameType));
-    stack.forEach(frameType -> internalRegisterUse(registry, frameType));
+    for (FrameType frameType : locals.values()) {
+      internalRegisterUse(registry, frameType);
+      if (registry.getTraversalContinuation().shouldBreak()) {
+        return;
+      }
+    }
+    for (FrameType frameType : stack) {
+      internalRegisterUse(registry, frameType);
+      if (registry.getTraversalContinuation().shouldBreak()) {
+        return;
+      }
+    }
   }
 
   private void internalRegisterUse(UseRegistry<?> registry, FrameType frameType) {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
index 09cabd8..44f3fde 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
@@ -293,9 +293,7 @@
       state =
           state.storeLocal(
               localIndex,
-              context.isInstanceInitializer(dexItemFactory)
-                      || context.mustBeInlinedIntoInstanceInitializer(appView)
-                      || context.isHorizontallyMergedInstanceInitializer(dexItemFactory)
+              context.isInstanceInitializerInlineIntoOrMerged(appView)
                   ? FrameType.uninitializedThis()
                   : FrameType.initializedNonNullReference(context.getHolderType()),
               config);
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 3bd5968..bb281b3 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -37,6 +37,7 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItem;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -62,8 +63,10 @@
 import com.android.tools.r8.utils.InternalGlobalSyntheticsProgramConsumer.InternalGlobalSyntheticsDexIndexedConsumer;
 import com.android.tools.r8.utils.InternalGlobalSyntheticsProgramConsumer.InternalGlobalSyntheticsDexPerFileConsumer;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.OriginalSourceFiles;
 import com.android.tools.r8.utils.PredicateUtils;
+import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -76,10 +79,12 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -92,7 +97,8 @@
   public final InternalOptions options;
   private final CodeToKeep desugaredLibraryCodeToKeep;
   private final Predicate<DexType> isTypeMissing;
-  public List<Marker> markers;
+  private final Optional<Marker> currentMarker;
+  public Collection<Marker> previousMarkers;
   public List<DexString> markerStrings;
   public Set<VirtualFile> globalSyntheticFiles;
 
@@ -168,21 +174,19 @@
     }
   }
 
-  public ApplicationWriter(AppView<?> appView, List<Marker> markers) {
-    this(appView, markers, null);
+  public ApplicationWriter(AppView<?> appView, Marker marker) {
+    this(appView, marker, null);
   }
 
-  public ApplicationWriter(
-      AppView<?> appView,
-      List<Marker> markers,
-      DexIndexedConsumer consumer) {
+  public ApplicationWriter(AppView<?> appView, Marker marker, DexIndexedConsumer consumer) {
     this.appView = appView;
     this.options = appView.options();
     this.desugaredLibraryCodeToKeep = CodeToKeep.createCodeToKeep(appView);
-    this.markers = markers;
+    this.currentMarker = Optional.ofNullable(marker);
     this.programConsumer = consumer;
     this.isTypeMissing =
         PredicateUtils.isNull(appView.appInfo()::definitionForWithoutExistenceAssert);
+    this.previousMarkers = appView.dexItemFactory().extractMarkers();
   }
 
   private NamingLens getNamingLens() {
@@ -315,8 +319,8 @@
         encodeChecksums(virtualFiles);
         timing.end();
       }
-      assert markers == null
-          || markers.isEmpty()
+      assert previousMarkers == null
+          || previousMarkers.isEmpty()
           || appView.dexItemFactory().extractMarkers() != null;
       assert appView.withProtoShrinker(
           shrinker -> virtualFiles.stream().allMatch(shrinker::verifyDeadProtoTypesNotReferenced),
@@ -403,26 +407,29 @@
 
   private void computeMarkerStrings(
       Box<ProguardMapId> delayedProguardMapId, List<LazyDexString> lazyDexStrings) {
-    if (markers != null && !markers.isEmpty()) {
-      int firstNonLazyMarker = 0;
-      if (willComputeProguardMap()) {
-        firstNonLazyMarker++;
-        lazyDexStrings.add(
-            new LazyDexString() {
-
-              @Override
-              public DexString internalCompute() {
-                Marker marker = markers.get(0);
-                marker.setPgMapId(delayedProguardMapId.get().getId());
-                return marker.toDexString(appView.dexItemFactory());
-              }
-            });
-      }
-      markerStrings = new ArrayList<>(markers.size() - firstNonLazyMarker);
-      for (int i = firstNonLazyMarker; i < markers.size(); i++) {
-        markerStrings.add(markers.get(i).toDexString(appView.dexItemFactory()));
-      }
+    List<Marker> allMarkers = new ArrayList<>();
+    if (previousMarkers != null) {
+      allMarkers.addAll(previousMarkers);
     }
+    DexItemFactory factory = appView.dexItemFactory();
+    currentMarker.ifPresent(
+        marker -> {
+          if (willComputeProguardMap()) {
+            lazyDexStrings.add(
+                new LazyDexString() {
+
+                  @Override
+                  public DexString internalCompute() {
+                    marker.setPgMapId(delayedProguardMapId.get().getId());
+                    return marker.toDexString(factory);
+                  }
+                });
+          } else {
+            allMarkers.add(marker);
+          }
+        });
+    allMarkers.sort(Comparator.comparing(Marker::toString));
+    markerStrings = ListUtils.map(allMarkers, marker -> marker.toDexString(factory));
   }
 
   private OriginalSourceFiles computeSourceFileString(
@@ -594,16 +601,19 @@
 
   public static void supplyAdditionalConsumers(AppView<?> appView) {
     InternalOptions options = appView.options();
+    Reporter reporter = options.reporter;
+    appView.getArtProfileCollection().supplyConsumers(appView);
     if (options.configurationConsumer != null) {
       ExceptionUtils.withConsumeResourceHandler(
-          options.reporter, options.configurationConsumer,
+          reporter,
+          options.configurationConsumer,
           options.getProguardConfiguration().getParsedConfiguration());
-      ExceptionUtils.withFinishedResourceHandler(options.reporter, options.configurationConsumer);
+      ExceptionUtils.withFinishedResourceHandler(reporter, options.configurationConsumer);
     }
     if (options.mainDexListConsumer != null) {
       ExceptionUtils.withConsumeResourceHandler(
-          options.reporter, options.mainDexListConsumer, writeMainDexList(appView));
-      ExceptionUtils.withFinishedResourceHandler(options.reporter, options.mainDexListConsumer);
+          reporter, options.mainDexListConsumer, writeMainDexList(appView));
+      ExceptionUtils.withFinishedResourceHandler(reporter, options.mainDexListConsumer);
     }
 
     KotlinModuleSynthesizer kotlinModuleSynthesizer = new KotlinModuleSynthesizer(appView);
@@ -642,13 +652,13 @@
                               .getBytes(),
                           AppServices.SERVICE_DIRECTORY_NAME + serviceName,
                           Origin.unknown()),
-                      options.reporter);
+                      reporter);
                 });
       }
       // Rewrite/synthesize kotlin_module files
       kotlinModuleSynthesizer
           .synthesizeKotlinModuleFiles()
-          .forEach(file -> dataResourceConsumer.accept(file, options.reporter));
+          .forEach(file -> dataResourceConsumer.accept(file, reporter));
     }
 
     if (options.featureSplitConfiguration != null) {
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 52eafdb..96a4596 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -1296,7 +1296,10 @@
       read = dexReader.get();
       os.write(read);
     } while (read != 0);
-    return dexItemFactory.createString(size, os.toByteArray());
+    byte[] content = os.toByteArray();
+    return Marker.hasMarkerPrefix(content)
+        ? dexItemFactory.createMarkerString(size, content)
+        : dexItemFactory.createString(size, content);
   }
 
   private DexType typeAt(int index) {
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java
index f2b1e15..911789d 100644
--- a/src/main/java/com/android/tools/r8/dex/Marker.java
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -4,20 +4,15 @@
 package com.android.tools.r8.dex;
 
 import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.errors.DesugaredLibraryMismatchDiagnostic;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 import com.google.gson.JsonSyntaxException;
 import java.util.Comparator;
-import java.util.HashSet;
 import java.util.Map.Entry;
-import java.util.Set;
 
 /** Abstraction for hidden dex marker intended for the main dex file. */
 public class Marker {
@@ -68,55 +63,6 @@
     this.jsonObject = jsonObject;
   }
 
-  public static void checkCompatibleDesugaredLibrary(Set<Marker> markers, Reporter reporter) {
-    if (markers.size() <= 1) {
-      return;
-    }
-    // In L8 compilation, the compilation has two markers, a L8 marker, which has a desugared
-    // library property, and either a D8 or a R8 marker, which has no desugared library property.
-    // In other compilations, the desugared library versions have to be consistent.
-    Set<String> desugaredLibraryIdentifiers = new HashSet<>();
-    for (Marker marker : markers) {
-      if (marker.tool == Tool.L8) {
-        assert marker.getDesugaredLibraryIdentifiers().length > 0;
-        assert markers.stream()
-            .allMatch(m -> m.tool == Tool.L8 || m.getDesugaredLibraryIdentifiers().length == 0);
-      } else {
-        String[] identifiers = marker.getDesugaredLibraryIdentifiers();
-        String identifier = null;
-        switch (identifiers.length) {
-          case 0:
-            // Only add the <no-library-desugaring> identifier for DEX. A marker from CF is
-            // assumed to go though desugaring for compiling to DEX, and that will introduce the
-            // DEX marker with the final library desugaring identifier.
-            if (marker.isDexBackend()) {
-              identifier = NO_LIBRARY_DESUGARING;
-            } else {
-              assert marker.isCfBackend();
-            }
-            break;
-          case 1:
-            identifier = identifiers[0];
-            break;
-          default:
-            // To be implemented once D8/R8 compilation supports multiple desugared libraries.
-            throw reporter.fatalError(
-                new StringDiagnostic(
-                    "Merging program compiled with multiple desugared libraries."));
-        }
-        if (marker.isDesugared() && identifier != null) {
-          desugaredLibraryIdentifiers.add(identifier);
-        } else {
-          assert identifier == null || identifier.equals(NO_LIBRARY_DESUGARING);
-        }
-      }
-    }
-
-    if (desugaredLibraryIdentifiers.size() > 1) {
-      reporter.error(new DesugaredLibraryMismatchDiagnostic(desugaredLibraryIdentifiers, markers));
-    }
-  }
-
   public Tool getTool() {
     return tool;
   }
@@ -311,9 +257,7 @@
   // Try to parse str as a marker.
   // Returns null if parsing fails.
   public static Marker parse(DexString dexString) {
-    if (dexString.size > 2
-        && dexString.content[0] == PREFIX_CHAR
-        && dexString.content[1] == PREFIX_CHAR) {
+    if (hasMarkerPrefix(dexString.content)) {
       String str = dexString.toString();
       if (str.startsWith(D8_PREFIX)) {
         return internalParse(Tool.D8, str.substring(D8_PREFIX.length() - 1));
@@ -328,6 +272,10 @@
     return null;
   }
 
+  public static boolean hasMarkerPrefix(byte[] content) {
+    return content.length > 2 && content[0] == PREFIX_CHAR && content[1] == PREFIX_CHAR;
+  }
+
   private static Marker internalParse(Tool tool, String str) {
     try {
       JsonElement result = new JsonParser().parse(str);
diff --git a/src/main/java/com/android/tools/r8/errors/CheckDiscardDiagnostic.java b/src/main/java/com/android/tools/r8/errors/CheckDiscardDiagnostic.java
index ce54533..5a9ebb3 100644
--- a/src/main/java/com/android/tools/r8/errors/CheckDiscardDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/CheckDiscardDiagnostic.java
@@ -66,7 +66,7 @@
     StringBuilder builder = new StringBuilder("Discard checks failed.");
     if (messages.size() > 0) {
       builder.append(System.lineSeparator());
-      builder.append("The following items where not discarded");
+      builder.append("The following items were not discarded");
       messages.forEach(
           message -> {
             builder.append(System.lineSeparator());
diff --git a/src/main/java/com/android/tools/r8/errors/DesugaredLibraryMismatchDiagnostic.java b/src/main/java/com/android/tools/r8/errors/DesugaredLibraryMismatchDiagnostic.java
index 5141753..f23cee7 100644
--- a/src/main/java/com/android/tools/r8/errors/DesugaredLibraryMismatchDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/DesugaredLibraryMismatchDiagnostic.java
@@ -8,16 +8,17 @@
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
+import java.util.Collection;
 import java.util.Set;
 import java.util.stream.Collectors;
 
 public class DesugaredLibraryMismatchDiagnostic implements Diagnostic {
 
   private final Set<String> desugaredLibraryIdentifiers;
-  private final Set<Marker> markers;
+  private final Collection<Marker> markers;
 
   public DesugaredLibraryMismatchDiagnostic(
-      Set<String> desugaredLibraryIdentifiers, Set<Marker> markers) {
+      Set<String> desugaredLibraryIdentifiers, Collection<Marker> markers) {
     this.desugaredLibraryIdentifiers = desugaredLibraryIdentifiers;
     this.markers = markers;
   }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
index 01f91e0..531c50e 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault;
 
 import com.android.tools.r8.startup.StartupProfileProvider;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SystemPropertyUtils;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Paths;
@@ -54,7 +53,7 @@
 
   private Collection<StartupProfileProvider> startupProfileProviders;
 
-  public StartupOptions(InternalOptions options) {
+  public StartupOptions() {
     this.startupProfileProviders =
         SystemPropertyUtils.applySystemProperty(
             "com.android.tools.r8.startup.profile",
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfile.java b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfile.java
index 6dd7c65..3a63098 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfile.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfile.java
@@ -7,10 +7,8 @@
 import com.android.tools.r8.TextInputStream;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.profile.art.AlwaysTrueArtProfileRulePredicate;
 import com.android.tools.r8.profile.art.ArtProfileBuilderUtils;
 import com.android.tools.r8.profile.art.ArtProfileBuilderUtils.SyntheticToSyntheticContextGeneralization;
-import com.android.tools.r8.profile.art.ArtProfileRulePredicate;
 import com.android.tools.r8.profile.art.HumanReadableArtProfileParser;
 import com.android.tools.r8.profile.art.HumanReadableArtProfileParserBuilder;
 import com.android.tools.r8.startup.StartupClassBuilder;
@@ -19,7 +17,6 @@
 import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.startup.SyntheticStartupMethodBuilder;
 import com.android.tools.r8.startup.diagnostic.MissingStartupProfileItemsDiagnostic;
-import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
 import java.util.ArrayList;
@@ -166,25 +163,14 @@
     public StartupProfileBuilder addHumanReadableArtProfile(
         TextInputStream textInputStream,
         Consumer<HumanReadableArtProfileParserBuilder> parserBuilderConsumer) {
-      Box<ArtProfileRulePredicate> rulePredicateBox =
-          new Box<>(new AlwaysTrueArtProfileRulePredicate());
-      parserBuilderConsumer.accept(
-          new HumanReadableArtProfileParserBuilder() {
-            @Override
-            public HumanReadableArtProfileParserBuilder setRulePredicate(
-                ArtProfileRulePredicate rulePredicate) {
-              rulePredicateBox.set(rulePredicate);
-              return this;
-            }
-          });
-
-      HumanReadableArtProfileParser parser =
+      HumanReadableArtProfileParser.Builder parserBuilder =
           HumanReadableArtProfileParser.builder()
               .setReporter(reporter)
               .setProfileBuilder(
                   ArtProfileBuilderUtils.createBuilderForArtProfileToStartupProfileConversion(
-                      this, rulePredicateBox.get(), syntheticToSyntheticContextGeneralization))
-              .build();
+                      this, syntheticToSyntheticContextGeneralization));
+      parserBuilderConsumer.accept(parserBuilder);
+      HumanReadableArtProfileParser parser = parserBuilder.build();
       parser.parse(textInputStream, startupProfileProvider.getOrigin());
       return this;
     }
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 1e6fb22..10620fc6 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -256,6 +256,12 @@
     promote(Constants.ACC_PUBLIC);
   }
 
+  public T withPublic() {
+    T newAccessFlags = copy();
+    newAccessFlags.promoteToPublic();
+    return newAccessFlags;
+  }
+
   public void promoteToStatic() {
     promote(Constants.ACC_STATIC);
   }
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 3633506..9c659f5 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -34,6 +34,7 @@
 import com.android.tools.r8.naming.SeedMapper;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
 import com.android.tools.r8.optimize.interfaces.collection.OpenClosedInterfacesCollection;
+import com.android.tools.r8.profile.art.ArtProfileCollection;
 import com.android.tools.r8.retrace.internal.RetraceUtils;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.AssumeInfoCollection;
@@ -76,6 +77,7 @@
   private T appInfo;
   private AppInfoWithClassHierarchy appInfoForDesugaring;
   private AppServices appServices;
+  private ArtProfileCollection artProfileCollection;
   private AssumeInfoCollection assumeInfoCollection = AssumeInfoCollection.builder().build();
   private final DontWarnConfiguration dontWarnConfiguration;
   private final WholeProgramOptimizations wholeProgramOptimizations;
@@ -135,21 +137,25 @@
   private final ComputedApiLevel computedMinApiLevel;
 
   private AppView(
-      T appInfo, WholeProgramOptimizations wholeProgramOptimizations, TypeRewriter mapper) {
-    this(appInfo, wholeProgramOptimizations, mapper, Timing.empty());
+      T appInfo,
+      ArtProfileCollection artProfileCollection,
+      WholeProgramOptimizations wholeProgramOptimizations,
+      TypeRewriter mapper) {
+    this(appInfo, artProfileCollection, wholeProgramOptimizations, mapper, Timing.empty());
   }
 
   private AppView(
       T appInfo,
+      ArtProfileCollection artProfileCollection,
       WholeProgramOptimizations wholeProgramOptimizations,
       TypeRewriter mapper,
       Timing timing) {
     assert appInfo != null;
+    this.appInfo = appInfo;
     this.context =
         timing.time(
-            "Compilation context",
-            () -> CompilationContext.createInitialContext(appInfo.options()));
-    this.appInfo = appInfo;
+            "Compilation context", () -> CompilationContext.createInitialContext(options()));
+    this.artProfileCollection = artProfileCollection;
     this.dontWarnConfiguration =
         timing.time(
             "Dont warn config",
@@ -192,12 +198,29 @@
   }
 
   public static <T extends AppInfo> AppView<T> createForD8(T appInfo) {
-    return new AppView<>(appInfo, WholeProgramOptimizations.OFF, defaultTypeRewriter(appInfo));
+    return new AppView<>(
+        appInfo,
+        ArtProfileCollection.createInitialArtProfileCollection(appInfo.options()),
+        WholeProgramOptimizations.OFF,
+        defaultTypeRewriter(appInfo));
+  }
+
+  public static <T extends AppInfo> AppView<T> createForSimulatingD8InR8(T appInfo) {
+    return new AppView<>(
+        appInfo,
+        ArtProfileCollection.empty(),
+        WholeProgramOptimizations.OFF,
+        defaultTypeRewriter(appInfo));
   }
 
   public static <T extends AppInfo> AppView<T> createForD8(
       T appInfo, TypeRewriter mapper, Timing timing) {
-    return new AppView<>(appInfo, WholeProgramOptimizations.OFF, mapper, timing);
+    return new AppView<>(
+        appInfo,
+        ArtProfileCollection.createInitialArtProfileCollection(appInfo.options()),
+        WholeProgramOptimizations.OFF,
+        mapper,
+        timing);
   }
 
   public static AppView<AppInfoWithClassHierarchy> createForR8(DexApplication application) {
@@ -216,20 +239,36 @@
             mainDexInfo,
             GlobalSyntheticsStrategy.forSingleOutputMode(),
             startupOrder);
-    return new AppView<>(appInfo, WholeProgramOptimizations.ON, defaultTypeRewriter(appInfo));
+    return new AppView<>(
+        appInfo,
+        ArtProfileCollection.createInitialArtProfileCollection(application.options),
+        WholeProgramOptimizations.ON,
+        defaultTypeRewriter(appInfo));
   }
 
   public static <T extends AppInfo> AppView<T> createForL8(T appInfo, TypeRewriter mapper) {
-    return new AppView<>(appInfo, WholeProgramOptimizations.OFF, mapper);
+    return new AppView<>(
+        appInfo,
+        ArtProfileCollection.createInitialArtProfileCollection(appInfo.options()),
+        WholeProgramOptimizations.OFF,
+        mapper);
   }
 
   public static <T extends AppInfo> AppView<T> createForRelocator(T appInfo) {
-    return new AppView<>(appInfo, WholeProgramOptimizations.OFF, defaultTypeRewriter(appInfo));
+    return new AppView<>(
+        appInfo,
+        ArtProfileCollection.empty(),
+        WholeProgramOptimizations.OFF,
+        defaultTypeRewriter(appInfo));
   }
 
   public static AppView<AppInfoWithClassHierarchy> createForTracer(
       AppInfoWithClassHierarchy appInfo) {
-    return new AppView<>(appInfo, WholeProgramOptimizations.ON, defaultTypeRewriter(appInfo));
+    return new AppView<>(
+        appInfo,
+        ArtProfileCollection.empty(),
+        WholeProgramOptimizations.ON,
+        defaultTypeRewriter(appInfo));
   }
 
   public AbstractValueFactory abstractValueFactory() {
@@ -315,6 +354,14 @@
     this.appServices = appServices;
   }
 
+  public ArtProfileCollection getArtProfileCollection() {
+    return artProfileCollection;
+  }
+
+  public void setArtProfileCollection(ArtProfileCollection artProfileCollection) {
+    this.artProfileCollection = artProfileCollection;
+  }
+
   public AssumeInfoCollection getAssumeInfoCollection() {
     return assumeInfoCollection;
   }
@@ -772,6 +819,7 @@
     if (appServices() != null) {
       setAppServices(appServices().prunedCopy(prunedItems));
     }
+    setArtProfileCollection(getArtProfileCollection().withoutPrunedItems(prunedItems));
     setAssumeInfoCollection(getAssumeInfoCollection().withoutPrunedItems(prunedItems));
     if (hasProguardCompatibilityActions()) {
       setProguardCompatibilityActions(
@@ -854,10 +902,11 @@
             appliedMemberRebindingLens.isMemberRebindingLens()
                 ? appliedMemberRebindingLens
                     .asMemberRebindingLens()
-                    .toRewrittenFieldRebindingLens(appView, appliedLens)
+                    .toRewrittenFieldRebindingLens(appView, appliedLens, appliedMemberRebindingLens)
                 : appliedMemberRebindingLens
                     .asMemberRebindingIdentityLens()
-                    .toRewrittenMemberRebindingIdentityLens(appView, appliedLens);
+                    .toRewrittenMemberRebindingIdentityLens(
+                        appView, appliedLens, appliedMemberRebindingLens);
       }
     }
 
@@ -870,6 +919,8 @@
                 .setAppInfo(appView.appInfoWithLiveness().rewrittenWithLens(application, lens));
           }
           appView.setAppServices(appView.appServices().rewrittenWithLens(lens));
+          appView.setArtProfileCollection(
+              appView.getArtProfileCollection().rewrittenWithLens(lens));
           appView.setAssumeInfoCollection(
               appView.getAssumeInfoCollection().rewrittenWithLens(appView, lens));
           if (appView.hasInitClassLens()) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java b/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
index 3fe50aa..be0c645 100644
--- a/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
@@ -12,7 +12,7 @@
 import java.io.PrintStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.List;
+import java.util.Collection;
 import java.util.function.Consumer;
 
 public abstract class DexByteCodeWriter {
@@ -49,7 +49,7 @@
   }
 
   public void writeMarkers(PrintStream output) {
-    List<Marker> markers = application.dexItemFactory.extractMarkers();
+    Collection<Marker> markers = application.dexItemFactory.extractMarkers();
     System.out.println("Number of markers: " + markers.size());
     for (Marker marker : markers) {
       output.println(marker.toString());
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index f6edecd..93011d5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -312,7 +312,8 @@
     return builder(this)
         .setField(field)
         .disableAndroidApiLevelCheckIf(
-            !appView.options().apiModelingOptions().enableApiCallerIdentification)
+            !appView.options().apiModelingOptions().enableApiCallerIdentification
+                || !appView.enableWholeProgramOptimizations())
         .apply(consumer)
         .build();
   }
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 05b5030..d68d906 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1125,6 +1125,7 @@
     return syntheticBuilder(this)
         .setMethod(newMethod)
         .modifyAccessFlags(MethodAccessFlags::setSynthetic)
+        .setGenericSignature(MethodTypeSignature.noSignature())
         // If the forwarding target is abstract, we can just create an abstract method. While it
         // will not actually forward, it will create the same exception when hit at runtime.
         // Otherwise, we need to create code that forwards the call to the target.
@@ -1132,7 +1133,6 @@
             !isAbstract(),
             builder ->
                 builder
-                    .setGenericSignature(MethodTypeSignature.noSignature())
                     .setCode(
                         ForwardMethodBuilder.builder(definitions.dexItemFactory())
                             .setStaticSource(newMethod)
@@ -1253,7 +1253,8 @@
     if (from.hasClassFileVersion()) {
       upgradeClassFileVersion(from.getClassFileVersion());
     }
-    if (appView.options().apiModelingOptions().enableApiCallerIdentification) {
+    if (appView.options().apiModelingOptions().enableApiCallerIdentification
+        && appView.enableWholeProgramOptimizations()) {
       apiLevelForCode = getApiLevelForCode().max(from.getApiLevelForCode());
     }
   }
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 bfa111e..9620ee4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -52,7 +52,9 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -79,6 +81,7 @@
   /** Set of types that may be synthesized during compilation. */
   private final Set<DexType> possibleCompilerSynthesizedTypes = Sets.newIdentityHashSet();
 
+  private final Map<DexString, DexString> markers = new ConcurrentHashMap<>();
   private final Map<DexString, DexString> strings = new ConcurrentHashMap<>();
   private final Map<DexString, DexType> types = new ConcurrentHashMap<>();
   private final Map<DexField, DexField> fields = new ConcurrentHashMap<>();
@@ -2319,6 +2322,22 @@
     return previous == null ? item : previous;
   }
 
+  public DexString createMarkerString(int size, byte[] content) {
+    DexString potentialMarker = createString(size, content);
+    if (Marker.hasMarkerPrefix(potentialMarker.content)) {
+      markers.put(potentialMarker, potentialMarker);
+    }
+    return potentialMarker;
+  }
+
+  public DexString createMarkerString(String marker) {
+    DexString potentialMarker = createString(marker);
+    if (Marker.hasMarkerPrefix(potentialMarker.content)) {
+      markers.put(potentialMarker, potentialMarker);
+    }
+    return potentialMarker;
+  }
+
   public DexString createString(int size, byte[] content) {
     assert !sorted;
     return canonicalize(strings, new DexString(size, content));
@@ -2598,10 +2617,9 @@
 
   // Debugging support to extract marking string.
   // Find all markers.
-  public synchronized List<Marker> extractMarkers() {
-    // This is slow but it is not needed for any production code yet.
-    List<Marker> markers = new ArrayList<>();
-    for (DexString dexString : strings.keySet()) {
+  public synchronized Collection<Marker> extractMarkers() {
+    Set<Marker> markers = new HashSet<>();
+    for (DexString dexString : this.markers.keySet()) {
       Marker marker = Marker.parse(dexString);
       if (marker != null) {
         markers.add(marker);
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index 2fbdbdc..fe2f81d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -317,6 +317,12 @@
     return getName().startsWith(dexItemFactory.syntheticConstructorMethodPrefix);
   }
 
+  public boolean isInstanceInitializerInlineIntoOrMerged(AppView<?> appView) {
+    return isInstanceInitializer(appView.dexItemFactory())
+        || mustBeInlinedIntoInstanceInitializer(appView)
+        || isHorizontallyMergedInstanceInitializer(appView.dexItemFactory());
+  }
+
   public DexMethod withExtraArgumentPrepended(DexType type, DexItemFactory dexItemFactory) {
     return dexItemFactory.createMethod(
         holder, dexItemFactory.prependTypeToProto(type, proto), name);
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 f648f90..6b9015b 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -142,7 +142,7 @@
                 CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX,
                 new char[reader.getMaxStringLength()]);
         if (maybeMarker instanceof String) {
-          application.getFactory().createString((String) maybeMarker);
+          application.getFactory().createMarkerString((String) maybeMarker);
         }
       } catch (IllegalArgumentException e) {
         // Ignore if the type of the constant is not something readConst() allows.
diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java
index f949105..6a72244 100644
--- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java
+++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoFixer;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
-import com.android.tools.r8.ir.optimize.info.bridge.VirtualBridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.BitSet;
@@ -39,7 +38,7 @@
    * changes were made.
    */
   @Override
-  public BridgeInfo fixupBridgeInfo(VirtualBridgeInfo bridgeInfo) {
+  public BridgeInfo fixupBridgeInfo(BridgeInfo bridgeInfo) {
     if (getArgumentInfoCollection().isEmpty()) {
       return bridgeInfo;
     }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index 0751f8e..ff9cb9e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -225,6 +225,8 @@
             .setField(group.getClassIdField())
             .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())
             .setApiLevel(appView.computedMinApiLevel())
+            .disableAndroidApiLevelCheckIf(
+                !appView.options().apiModelingOptions().isApiCallerIdentificationEnabled())
             .build();
 
     // For the $r8$classId synthesized fields, we try to over-approximate the set of values it may
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java
index 162ec76..8718185 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java
@@ -45,7 +45,7 @@
       // of the class initializers without applying the unapplied code rewritings, to avoid that we
       // apply the lens more than once to the same piece of code.
       AppView<AppInfo> appViewForConversion =
-          AppView.createForD8(
+          AppView.createForSimulatingD8InR8(
               AppInfo.createInitialAppInfo(
                   appView.appInfo().app(), GlobalSyntheticsStrategy.forNonSynthesizing()));
       appViewForConversion.setGraphLens(appView.graphLens());
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
index f669473..19d4450 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
@@ -263,7 +263,6 @@
   }
 
   private MethodAccessFlags getNewAccessFlags() {
-    // TODO(b/164998929): ensure this behaviour is correct, should probably calculate upper bound
     return MethodAccessFlags.fromSharedAccessFlags(
         Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, true);
   }
@@ -281,7 +280,7 @@
           needsClassId,
           extraNulls);
     }
-    if (isSingleton() && !group.hasClassIdField()) {
+    if (!useSyntheticMethod()) {
       return getRepresentative().getDefinition().getCode();
     }
     return new ConstructorEntryPointSynthesizedCode(
@@ -328,7 +327,7 @@
     // Move instance initializers to target class.
     if (hasInstanceInitializerDescription()) {
       lensBuilder.moveMethods(instanceInitializers, newMethodReference);
-    } else if (isSingleton() && !group.hasClassIdField()) {
+    } else if (!useSyntheticMethod()) {
       lensBuilder.moveMethod(representative.getReference(), newMethodReference, true);
     } else {
       for (ProgramMethod instanceInitializer : instanceInitializers) {
@@ -343,7 +342,8 @@
     // Add a mapping from a synthetic name to the synthetic constructor.
     DexMethod syntheticMethodReference =
         getSyntheticMethodReference(classMethodsBuilder, newMethodReference);
-    if (!isSingleton() || group.hasClassIdField()) {
+
+    if (useSyntheticMethod()) {
       lensBuilder.recordNewMethodSignature(syntheticMethodReference, newMethodReference, true);
     }
 
@@ -360,10 +360,14 @@
     }
 
     DexEncodedMethod representativeMethod = representative.getDefinition();
+    boolean useSynthethicBuilder = useSyntheticMethod() || representativeMethod.isD8R8Synthesized();
     DexEncodedMethod newInstanceInitializer =
-        DexEncodedMethod.syntheticBuilder()
+        (useSynthethicBuilder ? DexEncodedMethod.syntheticBuilder() : DexEncodedMethod.builder())
             .setMethod(newMethodReference)
-            .setAccessFlags(getNewAccessFlags())
+            .setAccessFlags(
+                useSynthethicBuilder
+                    ? getNewAccessFlags()
+                    : representative.getAccessFlags().withPublic())
             .setCode(
                 getNewCode(
                     newMethodReference,
@@ -387,4 +391,8 @@
       }
     }
   }
+
+  private boolean useSyntheticMethod() {
+    return !isSingleton() || group.hasClassIdField();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDifferentApiReferenceLevel.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDifferentApiReferenceLevel.java
index 992f73c..8605caa 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDifferentApiReferenceLevel.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDifferentApiReferenceLevel.java
@@ -23,7 +23,7 @@
     this.appView = appView;
     apiLevelCompute = appView.apiLevelCompute();
     enableApiCallerIdentification =
-        appView.options().apiModelingOptions().enableApiCallerIdentification;
+        appView.options().apiModelingOptions().isApiCallerIdentificationEnabled();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
index 5bc0f58..e3986c8 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
@@ -62,6 +63,18 @@
       }
     }
 
+    // If any field has a non-public type, then field merging may lead to check-cast instructions
+    // being synthesized. These synthesized accesses depends on the package.
+    for (DexEncodedField field : clazz.fields()) {
+      DexType fieldBaseType = field.getType().toBaseType(appView.dexItemFactory());
+      if (fieldBaseType.isClassType()) {
+        DexClass fieldBaseClass = appView.definitionFor(fieldBaseType);
+        if (fieldBaseClass == null || !fieldBaseClass.isPublic()) {
+          return true;
+        }
+      }
+    }
+
     // Check that all accesses from [clazz] to classes or members from the current package of
     // [clazz] will continue to work. This is guaranteed if the methods of [clazz] do not access
     // any private or protected classes or members from the current package of [clazz].
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index 4d9b9a0..74a3fb5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -42,6 +42,7 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.DependentMinimumKeepInfoCollection;
+import com.android.tools.r8.shaking.KeepMethodInfo;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Sets;
@@ -87,9 +88,10 @@
       ProgramMethod dynamicMethod =
           generatedMessageLiteClass.lookupProgramMethod(references.dynamicMethod);
       if (dynamicMethod != null) {
-        dependentMinimumKeepInfo
-            .getOrCreateUnconditionalMinimumKeepInfoFor(dynamicMethod.getReference())
-            .disallowOptimization();
+        disallowSignatureOptimizations(
+            dependentMinimumKeepInfo
+                .getOrCreateUnconditionalMinimumKeepInfoFor(dynamicMethod.getReference())
+                .asMethodJoiner());
       }
 
       references.forEachMethodReference(
@@ -98,14 +100,27 @@
                 asProgramClassOrNull(appView.definitionFor(reference.getHolderType()));
             ProgramMethod method = reference.lookupOnProgramClass(holder);
             if (method != null) {
-              dependentMinimumKeepInfo
-                  .getOrCreateUnconditionalMinimumKeepInfoFor(method.getReference())
-                  .disallowOptimization();
+              disallowSignatureOptimizations(
+                  dependentMinimumKeepInfo
+                      .getOrCreateUnconditionalMinimumKeepInfoFor(method.getReference())
+                      .asMethodJoiner());
             }
           });
     }
   }
 
+  private void disallowSignatureOptimizations(KeepMethodInfo.Joiner methodJoiner) {
+    methodJoiner
+        .disallowConstantArgumentOptimization()
+        .disallowMethodStaticizing()
+        .disallowParameterRemoval()
+        .disallowParameterReordering()
+        .disallowParameterTypeStrengthening()
+        .disallowReturnTypeStrengthening()
+        .disallowUnusedArgumentOptimization()
+        .disallowUnusedReturnValueOptimization();
+  }
+
   public void run(IRCode code) {
     ProgramMethod method = code.context();
     if (references.isDynamicMethod(method.getReference())) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
index c83f118..dfc9eb3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
@@ -78,6 +78,10 @@
     return deadProtoTypes;
   }
 
+  public ProtoReferences getProtoReferences() {
+    return references;
+  }
+
   public void setDeadProtoTypes(Set<DexType> deadProtoTypes) {
     // We should only need to keep track of the dead proto types for assertion purposes.
     InternalOptions.checkAssertionsEnabled();
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index 76e9d15..182b9b4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -177,7 +177,7 @@
   }
 
   @Override
-  public void buildLIR(LIRBuilder<Value> builder) {
+  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
     builder.addArgument(index, knownToBeBoolean);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index 14837ee..5715e54 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
+import com.android.tools.r8.lightir.LIRBuilder;
 
 public class ArrayLength extends Instruction {
 
@@ -151,4 +152,9 @@
   public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
     return false;
   }
+
+  @Override
+  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
+    builder.addArrayLength(array());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 550ea8b..a214f7c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -14,6 +14,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -2114,4 +2116,19 @@
       phiIt.remove();
     }
   }
+
+  public void registerUse(UseRegistry<?> registry, ProgramMethod method) {
+    for (Instruction instruction : instructions) {
+      instruction.registerUse(registry, method);
+      if (registry.getTraversalContinuation().shouldBreak()) {
+        return;
+      }
+    }
+    for (DexType guard : catchHandlers.getGuards()) {
+      registry.registerExceptionGuard(guard);
+      if (registry.getTraversalContinuation().shouldBreak()) {
+        return;
+      }
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java b/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
index 03771cd..6d8a713 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
@@ -29,6 +29,10 @@
     public T getTarget() {
       return target;
     }
+
+    public DexType getGuard() {
+      return guard;
+    }
   }
 
   private final List<DexType> guards;
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index ea014e6..d9a1063 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -16,8 +16,10 @@
 import com.android.tools.r8.graph.AccessControl;
 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.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -278,6 +280,11 @@
     return false;
   }
 
+  @Override
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    registry.registerCheckCast(type, ignoreCompatRules);
+  }
+
   public static class Builder extends BuilderBase<Builder, CheckCast> {
 
     protected DexType castType;
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 1fd0fed..2dfbc18 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -13,8 +13,10 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 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.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -213,6 +215,11 @@
     return UnknownValue.getInstance();
   }
 
+  @Override
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    registry.registerConstClass(clazz, null, ignoreCompatRules);
+  }
+
   public static class Builder extends BuilderBase<Builder, ConstClass> {
 
     private DexType type;
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
index 78ee74e..15afa68 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
@@ -3,15 +3,19 @@
 // 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.UseRegistry.MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
+
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.cf.code.CfConstMethodHandle;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.code.DexConstMethodHandle;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -129,4 +133,9 @@
   public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
     helper.storeOutValue(this, it);
   }
+
+  @Override
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    registry.registerMethodHandle(methodHandle, NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
index 5ce3e31..0b586ca 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
@@ -9,9 +9,11 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.code.DexConstMethodType;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -129,4 +131,9 @@
       InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forConstMethodType();
   }
+
+  @Override
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    registry.registerProto(methodType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index 8d39b43..c462cf6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.lightir.LIRBuilder;
 import com.android.tools.r8.utils.InternalOutputMode;
 import com.android.tools.r8.utils.NumberUtils;
 import java.util.Set;
@@ -343,4 +344,9 @@
       AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
     return appView.abstractValueFactory().createSingleNumberValue(value);
   }
+
+  @Override
+  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
+    builder.addConstNumber(outType(), value);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index afb3203..900fe91 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -182,7 +182,7 @@
   }
 
   @Override
-  public void buildLIR(LIRBuilder<Value> builder) {
+  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
     builder.addConstString(value);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index 1f87da5..651fb35 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -101,7 +101,7 @@
   }
 
   @Override
-  public void buildLIR(LIRBuilder<Value> builder) {
+  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
     builder.addDebugPosition(getPosition());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
index 78a582b..3a5623d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -9,9 +9,11 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -170,4 +172,12 @@
         .abstractValueFactory()
         .createSingleDexItemBasedStringValue(item, nameComputationInfo);
   }
+
+  @Override
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    if (nameComputationInfo.needsToRegisterReference()) {
+      assert item.isDexType();
+      registry.registerTypeReference(item.asDexType());
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Div.java b/src/main/java/com/android/tools/r8/ir/code/Div.java
index 21031fb..4f99960 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Div.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Div.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.dex.code.DexInstruction;
 import com.android.tools.r8.ir.analysis.constant.Bottom;
 import com.android.tools.r8.ir.analysis.constant.LatticeElement;
+import com.android.tools.r8.lightir.LIRBuilder;
 import java.util.function.Function;
 
 public class Div extends ArithmeticBinop {
@@ -148,4 +149,9 @@
   CfArithmeticBinop.Opcode getCfOpcode() {
     return CfArithmeticBinop.Opcode.Div;
   }
+
+  @Override
+  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
+    builder.addDiv(type, leftValue(), rightValue());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Goto.java b/src/main/java/com/android/tools/r8/ir/code/Goto.java
index 025dec0..b953a68 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Goto.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Goto.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.cf.code.CfGoto;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.lightir.LIRBuilder;
 import com.android.tools.r8.utils.CfgPrinter;
 import java.util.List;
 import java.util.ListIterator;
@@ -126,6 +127,11 @@
     return true;
   }
 
+  @Override
+  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
+    builder.addGoto(getTarget());
+  }
+
   public static class Builder extends BuilderBase<Builder, Goto> {
 
     private BasicBlock target;
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index dbcd9be..7ec066c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.graph.classmerging.MergedClassesCollection;
 import com.android.tools.r8.ir.analysis.TypeChecker;
 import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
@@ -1514,4 +1515,18 @@
       }
     }
   }
+
+  /**
+   * Note: This will discard instructions that are not present on the lower level code items, such
+   * as assume.
+   */
+  public void registerCodeReferences(ProgramMethod method, UseRegistry<ProgramMethod> registry) {
+    assert registry.getTraversalContinuation().shouldContinue();
+    for (BasicBlock block : blocks) {
+      block.registerUse(registry, method);
+      if (registry.getTraversalContinuation().shouldBreak()) {
+        return;
+      }
+    }
+  }
 }
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 9269104..12089ec 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
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.lightir.LIRBuilder;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.InternalOutputMode;
@@ -289,4 +290,16 @@
     assert inValues.get(0).outType() == inValues.get(1).outType();
     builder.add(new CfIfCmp(type, ifType, builder.getLabel(getTrueTarget())), this);
   }
+
+  @Override
+  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
+    ValueType ifType = inValues.get(0).outType();
+    if (inValues.size() == 1) {
+      builder.addIf(type, ifType, inValues.get(0), getTrueTarget());
+      return;
+    }
+    assert inValues.size() == 2;
+    assert inValues.get(0).outType() == inValues.get(1).outType();
+    builder.addIfCmp(type, ifType, inValues, getTrueTarget());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InitClass.java b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
index f331383..765d4a1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InitClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
@@ -11,8 +11,10 @@
 import com.android.tools.r8.graph.AccessControl;
 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.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
@@ -171,6 +173,11 @@
     return super.toString() + "; " + clazz.toSourceString();
   }
 
+  @Override
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    registry.registerInitClass(clazz);
+  }
+
   public static class Builder extends BuilderBase<Builder, InitClass> {
 
     private DexType type;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 6f571d0..d01444f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -18,9 +18,11 @@
 import com.android.tools.r8.dex.code.DexInstruction;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
@@ -259,6 +261,11 @@
     return true;
   }
 
+  @Override
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    registry.registerInstanceFieldRead(getField());
+  }
+
   public static class Builder extends BuilderBase<Builder, InstanceGet> {
 
     private DexField field;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
index 4af4442..680ace0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
@@ -9,8 +9,10 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.code.DexInstanceOf;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -128,4 +130,9 @@
     StringBuilder builder = new StringBuilder(super.toString());
     return builder.append("; ").append(type.toSourceString()).toString();
   }
+
+  @Override
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    registry.registerInstanceOf(type);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index be3739e..bc4d8df 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -17,11 +17,13 @@
 import com.android.tools.r8.dex.code.DexIputWide;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
@@ -276,4 +278,9 @@
   public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
     return false;
   }
+
+  @Override
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    registry.registerInstanceFieldWrite(getField());
+  }
 }
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 55d96c5..c99ee11 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
@@ -10,9 +10,11 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
 import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
@@ -1557,10 +1559,18 @@
     return false;
   }
 
-  public void buildLIR(LIRBuilder<Value> builder) {
+  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
     throw new Unimplemented("Missing impl for " + getClass().getSimpleName());
   }
 
+  public void registerUse(UseRegistry registry, ProgramMethod context) {
+    internalRegisterUse(registry, context);
+  }
+
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    // Intentionally empty.
+  }
+
   public static class SideEffectAssumption {
 
     public static final SideEffectAssumption NONE = new SideEffectAssumption();
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index 786a73e..80bb77b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -116,6 +116,10 @@
 
       MethodLookupResult lookupResult =
           graphLens.lookupMethod(invokedMethod, context.getReference(), Type.DIRECT);
+      if (lookupResult.getType().isStatic()) {
+        // This method has been staticized. The original invoke-type is DIRECT.
+        return Type.DIRECT;
+      }
       if (lookupResult.getType().isVirtual()) {
         // This method has been publicized. The original invoke-type is DIRECT.
         return Type.DIRECT;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index e43b0f0..75a13c9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -11,8 +11,10 @@
 import com.android.tools.r8.dex.code.DexInvokeCustomRange;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.InterfaceCollection;
 import com.android.tools.r8.ir.analysis.type.InterfaceCollection.Builder;
@@ -204,4 +206,9 @@
   public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
     return true;
   }
+
+  @Override
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    registry.registerCallSite(callSite);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index 25d775a..9203e93 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
@@ -217,10 +218,15 @@
   }
 
   @Override
-  public void buildLIR(LIRBuilder<Value> builder) {
+  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
     builder.addInvokeDirect(getInvokedMethod(), arguments());
   }
 
+  @Override
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    registry.registerInvokeDirect(getInvokedMethod());
+  }
+
   public static class Builder extends InvokeMethod.Builder<Builder, InvokeDirect> {
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
index 67e78d6..be6eac1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
@@ -140,4 +141,9 @@
     return ClassInitializationAnalysis.InstructionUtils.forInvokeInterface(
         this, clazz, context, appView, mode, assumption);
   }
+
+  @Override
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    registry.registerInvokeInterface(getInvokedMethod());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index 7ac70e7..de12658 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -11,8 +11,10 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 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.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -186,4 +188,9 @@
   public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
     return false;
   }
+
+  @Override
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    registry.registerTypeReference(type);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index 5a3a1118..a2e815f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -13,8 +13,10 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 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.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -214,4 +216,9 @@
   public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
     return false;
   }
+
+  @Override
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    registry.registerTypeReference(type);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 9e6a918..ecc7327 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
@@ -237,6 +238,11 @@
         .classInitializationMayHaveSideEffectsInContext(appView, context);
   }
 
+  @Override
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    registry.registerInvokeStatic(getInvokedMethod());
+  }
+
   public static class Builder extends InvokeMethod.Builder<Builder, InvokeStatic> {
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
index 705c7f9..ae47eb6 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
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
@@ -134,4 +135,9 @@
     return ClassInitializationAnalysis.InstructionUtils.forInvokeSuper(
         this, clazz, context, appView, mode, assumption);
   }
+
+  @Override
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    registry.registerInvokeSuper(getInvokedMethod());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index f44d128..0cbf370 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
@@ -164,6 +165,11 @@
         this, clazz, context, appView, mode, assumption);
   }
 
+  @Override
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    registry.registerInvokeVirtual(getInvokedMethod());
+  }
+
   public static class Builder extends InvokeMethod.Builder<Builder, InvokeVirtual> {
 
     @Override
@@ -178,7 +184,7 @@
   }
 
   @Override
-  public void buildLIR(LIRBuilder<Value> builder) {
+  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
     builder.addInvokeVirtual(getInvokedMethod(), arguments());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 616882a..acd2cc4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LIRBuilder;
 import com.android.tools.r8.utils.InternalOptions;
 
 public class MoveException extends Instruction {
@@ -131,4 +132,9 @@
   public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
     return false;
   }
+
+  @Override
+  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
+    builder.addMoveException(exceptionType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index 88bd18d..c6d527f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -10,8 +10,10 @@
 import com.android.tools.r8.dex.code.DexNewArray;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -164,4 +166,9 @@
   public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
     return false;
   }
+
+  @Override
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    registry.registerTypeReference(type);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 236bd0a..40e7d5d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -12,11 +12,13 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 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.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
@@ -232,6 +234,11 @@
     return true;
   }
 
+  @Override
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    registry.registerNewInstance(clazz);
+  }
+
   public static class Builder extends BuilderBase<Builder, NewInstance> {
 
     private DexType type;
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java
index 2274754..123b988 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java
@@ -9,8 +9,10 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.code.DexNewUnboxedEnumInstance;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -159,4 +161,9 @@
     assert type.isDefinitelyNotNull();
     return true;
   }
+
+  @Override
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    registry.registerNewUnboxedEnumInstance(clazz);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index 935e9df..d8882e4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -153,7 +153,7 @@
   }
 
   @Override
-  public void buildLIR(LIRBuilder<Value> builder) {
+  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
     if (hasReturnValue()) {
       builder.addReturn(returnValue());
     } else {
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index 19b9b27..afad1ff 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -18,9 +18,11 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
@@ -268,6 +270,11 @@
     }
   }
 
+  @Override
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    registry.registerStaticFieldRead(getField());
+  }
+
   public static class Builder extends BuilderBase<Builder, StaticGet> {
 
     private DexField field;
@@ -293,7 +300,7 @@
   }
 
   @Override
-  public void buildLIR(LIRBuilder<Value> builder) {
+  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
     builder.addStaticGet(getField());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index 7d54dbb..7e18e04 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -16,11 +16,13 @@
 import com.android.tools.r8.dex.code.DexSputWide;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
@@ -250,4 +252,9 @@
       return holder != context.getHolderType();
     }
   }
+
+  @Override
+  void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
+    registry.registerStaticFieldWrite(getField());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index a62fd2f..23e1ab3 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -199,7 +199,7 @@
   }
 
   private Set<UninitializedThisLocalRead> insertUninitializedThisLocalReads() {
-    if (!method.getDefinition().isInstanceInitializer()) {
+    if (!method.getReference().isInstanceInitializerInlineIntoOrMerged(appView)) {
       return Collections.emptySet();
     }
     // Find all non-normal exit blocks.
@@ -246,11 +246,13 @@
     assert initializers == null;
     assert thisInitializers == null;
     initializers = new HashMap<>();
+    boolean isInstanceInitializer =
+        method.getReference().isInstanceInitializerInlineIntoOrMerged(appView);
     for (BasicBlock block : code.blocks) {
       for (Instruction insn : block.getInstructions()) {
         if (insn.isNewInstance()) {
           initializers.put(insn.asNewInstance(), computeInitializers(insn.outValue()));
-        } else if (insn.isArgument() && method.getDefinition().isInstanceInitializer()) {
+        } else if (insn.isArgument() && isInstanceInitializer) {
           if (insn.outValue().isThis()) {
             // By JVM8 §4.10.1.9 (invokespecial), a this() or super() call in a constructor
             // changes the type of `this` from uninitializedThis
@@ -260,7 +262,7 @@
         }
       }
     }
-    assert !(method.getDefinition().isInstanceInitializer() && thisInitializers == null);
+    assert !(isInstanceInitializer && thisInitializers == null);
   }
 
   private List<InvokeDirect> computeInitializers(Value value) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index c62d786..ab044e3 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -49,6 +49,7 @@
 import com.android.tools.r8.ir.desugar.itf.L8InnerOuterAttributeEraser;
 import com.android.tools.r8.ir.desugar.lambda.LambdaDeserializationMethodRemover;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
+import com.android.tools.r8.ir.optimize.AssertionErrorTwoArgsConstructorRewriter;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.AssumeInserter;
 import com.android.tools.r8.ir.optimize.CheckNotNullConverter;
@@ -62,6 +63,7 @@
 import com.android.tools.r8.ir.optimize.IdempotentFunctionCallCanonicalizer;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InstanceInitializerOutliner;
 import com.android.tools.r8.ir.optimize.NaturalIntLoopRemover;
 import com.android.tools.r8.ir.optimize.RedundantFieldLoadAndStoreElimination;
 import com.android.tools.r8.ir.optimize.ReflectionOptimizer;
@@ -131,6 +133,7 @@
   private final InternalOptions options;
   private final CfgPrinter printer;
   public final CodeRewriter codeRewriter;
+  public final AssertionErrorTwoArgsConstructorRewriter assertionErrorTwoArgsConstructorRewriter;
   private final NaturalIntLoopRemover naturalIntLoopRemover = new NaturalIntLoopRemover();
   public final MemberValuePropagation<?> memberValuePropagation;
   private final LensCodeRewriter lensCodeRewriter;
@@ -143,6 +146,7 @@
   private final ServiceLoaderRewriter serviceLoaderRewriter;
   private final EnumValueOptimizer enumValueOptimizer;
   private final EnumUnboxer enumUnboxer;
+  private final InstanceInitializerOutliner instanceInitializerOutliner;
 
   public final AssumeInserter assumeInserter;
   private final DynamicTypeOptimization dynamicTypeOptimization;
@@ -179,6 +183,8 @@
     this.options = appView.options();
     this.printer = printer;
     this.codeRewriter = new CodeRewriter(appView);
+    this.assertionErrorTwoArgsConstructorRewriter =
+        new AssertionErrorTwoArgsConstructorRewriter(appView);
     this.classInitializerDefaultsOptimization =
         new ClassInitializerDefaultsOptimization(appView, this);
     this.stringOptimizer = new StringOptimizer(appView);
@@ -227,6 +233,7 @@
       this.enumValueOptimizer = null;
       this.enumUnboxer = EnumUnboxer.empty();
       this.assumeInserter = null;
+      this.instanceInitializerOutliner = null;
       return;
     }
     this.instructionDesugaring =
@@ -237,6 +244,12 @@
         options.processCovariantReturnTypeAnnotations
             ? new CovariantReturnTypeAnnotationTransformer(this, appView.dexItemFactory())
             : null;
+    if (appView.options().desugarState.isOn()
+        && appView.options().apiModelingOptions().enableOutliningOfMethods) {
+      this.instanceInitializerOutliner = new InstanceInitializerOutliner(appView);
+    } else {
+      this.instanceInitializerOutliner = null;
+    }
     if (appView.enableWholeProgramOptimizations()) {
       assert appView.appInfo().hasLiveness();
       assert appView.rootSet() != null;
@@ -358,6 +371,14 @@
     reportNestDesugarDependencies();
     clearNestAttributes();
 
+    if (instanceInitializerOutliner != null) {
+      processSimpleSynthesizeMethods(instanceInitializerOutliner.getSynthesizedMethods(), executor);
+    }
+    if (assertionErrorTwoArgsConstructorRewriter != null) {
+      processSimpleSynthesizeMethods(
+          assertionErrorTwoArgsConstructorRewriter.getSynthesizedMethods(), executor);
+    }
+
     application = commitPendingSyntheticItemsD8(appView, application);
 
     postProcessingDesugaringForD8(methodProcessor, interfaceProcessor, executor);
@@ -780,10 +801,19 @@
     builder.setHighestSortingString(highestSortingString);
 
     if (serviceLoaderRewriter != null) {
-      processSynthesizedServiceLoaderMethods(
+      processSimpleSynthesizeMethods(
           serviceLoaderRewriter.getServiceLoadMethods(), executorService);
     }
 
+    if (instanceInitializerOutliner != null) {
+      processSimpleSynthesizeMethods(
+          instanceInitializerOutliner.getSynthesizedMethods(), executorService);
+    }
+    if (assertionErrorTwoArgsConstructorRewriter != null) {
+      processSimpleSynthesizeMethods(
+          assertionErrorTwoArgsConstructorRewriter.getSynthesizedMethods(), executorService);
+    }
+
     // Update optimization info for all synthesized methods at once.
     feedback.updateVisibleOptimizationInfo();
 
@@ -865,14 +895,14 @@
     return onWaveDoneActions != null;
   }
 
-  private void processSynthesizedServiceLoaderMethods(
+  private void processSimpleSynthesizeMethods(
       List<ProgramMethod> serviceLoadMethods, ExecutorService executorService)
       throws ExecutionException {
     ThreadUtils.processItems(
-        serviceLoadMethods, this::forEachSynthesizedServiceLoaderMethod, executorService);
+        serviceLoadMethods, this::processAndFinalizeSimpleSynthesiedMethod, executorService);
   }
 
-  private void forEachSynthesizedServiceLoaderMethod(ProgramMethod method) {
+  private void processAndFinalizeSimpleSynthesiedMethod(ProgramMethod method) {
     IRCode code = method.buildIR(appView);
     assert code != null;
     codeRewriter.rewriteMoveResult(code);
@@ -1208,6 +1238,12 @@
       timing.end();
     }
 
+    if (instanceInitializerOutliner != null) {
+      instanceInitializerOutliner.rewriteInstanceInitializers(
+          code, context, methodProcessingContext);
+      assert code.verifyTypes(appView);
+    }
+
     previous = printMethod(code, "IR after disable assertions (SSA)", previous);
 
     // Update the IR code if collected call site optimization info has something useful.
@@ -1309,7 +1345,7 @@
     naturalIntLoopRemover.run(appView, code);
     timing.end();
     timing.begin("Rewrite AssertionError");
-    codeRewriter.rewriteAssertionErrorTwoArgumentConstructor(code, options);
+    assertionErrorTwoArgsConstructorRewriter.rewrite(code, methodProcessingContext);
     timing.end();
     timing.begin("Run CSE");
     codeRewriter.commonSubexpressionElimination(code);
@@ -1620,10 +1656,14 @@
       code = roundtripThroughLIR(code, feedback, bytecodeMetadataProvider, timing);
     }
     if (options.isGeneratingClassFiles()) {
+      timing.begin("IR->CF");
       finalizeToCf(code, feedback, bytecodeMetadataProvider, timing);
+      timing.end();
     } else {
       assert options.isGeneratingDex();
+      timing.begin("IR->DEX");
       finalizeToDex(code, feedback, bytecodeMetadataProvider, timing);
+      timing.end();
     }
   }
 
@@ -1632,8 +1672,12 @@
       OptimizationFeedback feedback,
       BytecodeMetadataProvider bytecodeMetadataProvider,
       Timing timing) {
-    LIRCode lirCode = IR2LIRConverter.translate(code);
+    timing.begin("IR->LIR");
+    LIRCode lirCode = IR2LIRConverter.translate(code, appView.dexItemFactory());
+    timing.end();
+    timing.begin("LIR->IR");
     IRCode irCode = LIR2IRConverter.translate(code.context(), lirCode, appView);
+    timing.end();
     return irCode;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
index 6137206..67ec80a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
@@ -41,7 +41,7 @@
         }
       case INVOKE_CONSTRUCTOR:
         {
-          forwardMethodBuilder.setConstructorTarget(target, appView.dexItemFactory());
+          forwardMethodBuilder.setConstructorTargetWithNewInstance(target);
           break;
         }
       default:
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 4412a67..e6faadc 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
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexApplication;
+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.DexMethod;
@@ -255,7 +256,12 @@
               assert theApi.equals(appView.options().getMinApiLevel());
               return;
             }
-            assert theApi.equals(api.max(appView.options().getMinApiLevel()));
+            DexClass clazz =
+                appView
+                    .contextIndependentDefinitionForWithResolutionResult(type)
+                    .toSingleClassWithProgramOverLibrary();
+            assert theApi.equals(api.max(appView.options().getMinApiLevel()))
+                || (clazz != null && clazz.isProgramClass());
           });
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index bf643ae..d2efd78 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -73,7 +73,9 @@
     factory.createSynthesizedType("Ljava/lang/NumberFormatException;");
     factory.createSynthesizedType("Ljava/lang/OutOfMemoryError;");
     factory.createSynthesizedType("Ljava/lang/Runnable;");
+    factory.createSynthesizedType("Ljava/lang/RuntimeException;");
     factory.createSynthesizedType("Ljava/lang/SecurityException;");
+    factory.createSynthesizedType("Ljava/lang/reflect/Constructor;");
     factory.createSynthesizedType("Ljava/lang/reflect/InvocationTargetException;");
     factory.createSynthesizedType("Ljava/lang/reflect/Method;");
     factory.createSynthesizedType("Ljava/util/AbstractMap$SimpleImmutableEntry;");
@@ -117,6 +119,101 @@
     factory.createSynthesizedType("[Ljava/util/Map$Entry;");
   }
 
+  public static CfCode AssertionErrorMethods_createAssertionError(
+      DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    CfLabel label6 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        5,
+        3,
+        ImmutableList.of(
+            label0,
+            new CfConstClass(factory.createType("Ljava/lang/AssertionError;")),
+            new CfConstNumber(2, ValueType.INT),
+            new CfNewArray(factory.createType("[Ljava/lang/Class;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfConstNumber(0, ValueType.INT),
+            new CfConstClass(factory.stringType),
+            new CfArrayStore(MemberType.OBJECT),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfConstNumber(1, ValueType.INT),
+            new CfConstClass(factory.throwableType),
+            new CfArrayStore(MemberType.OBJECT),
+            label1,
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.classType,
+                    factory.createProto(
+                        factory.createType("Ljava/lang/reflect/Constructor;"),
+                        factory.createType("[Ljava/lang/Class;")),
+                    factory.createString("getDeclaredConstructor")),
+                false),
+            new CfStore(ValueType.OBJECT, 2),
+            label2,
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfConstNumber(2, ValueType.INT),
+            new CfNewArray(factory.createType("[Ljava/lang/Object;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfConstNumber(0, ValueType.INT),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfArrayStore(MemberType.OBJECT),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfConstNumber(1, ValueType.INT),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfArrayStore(MemberType.OBJECT),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/reflect/Constructor;"),
+                    factory.createProto(
+                        factory.objectType, factory.createType("[Ljava/lang/Object;")),
+                    factory.createString("newInstance")),
+                false),
+            new CfCheckCast(factory.createType("Ljava/lang/AssertionError;")),
+            label3,
+            new CfReturn(ValueType.OBJECT),
+            label4,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(factory.stringType),
+                      FrameType.initializedNonNullReference(factory.throwableType)
+                    }),
+                new ArrayDeque<>(
+                    Arrays.asList(
+                        FrameType.initializedNonNullReference(
+                            factory.createType("Ljava/lang/Exception;"))))),
+            new CfStore(ValueType.OBJECT, 2),
+            label5,
+            new CfNew(factory.createType("Ljava/lang/AssertionError;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                183,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/AssertionError;"),
+                    factory.createProto(factory.voidType, factory.objectType),
+                    factory.createString("<init>")),
+                false),
+            new CfReturn(ValueType.OBJECT),
+            label6),
+        ImmutableList.of(
+            new CfTryCatch(
+                label0,
+                label3,
+                ImmutableList.of(factory.createType("Ljava/lang/Exception;")),
+                ImmutableList.of(label4))),
+        ImmutableList.of());
+  }
+
   public static CfCode AtomicReferenceArrayMethods_compareAndSet(
       DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
@@ -781,7 +878,7 @@
                             factory.createType("Ljava/lang/Exception;"))))),
             new CfStore(ValueType.OBJECT, 2),
             label6,
-            new CfNew(factory.createType("Ljava/lang/AssertionError;")),
+            new CfNew(factory.createType("Ljava/lang/RuntimeException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(factory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -826,7 +923,7 @@
             new CfInvoke(
                 183,
                 factory.createMethod(
-                    factory.createType("Ljava/lang/AssertionError;"),
+                    factory.createType("Ljava/lang/RuntimeException;"),
                     factory.createProto(
                         factory.voidType, factory.stringType, factory.throwableType),
                     factory.createString("<init>")),
@@ -844,7 +941,7 @@
                     Arrays.asList(FrameType.initializedNonNullReference(factory.throwableType)))),
             new CfStore(ValueType.OBJECT, 2),
             label8,
-            new CfNew(factory.createType("Ljava/lang/AssertionError;")),
+            new CfNew(factory.createType("Ljava/lang/RuntimeException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(factory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -889,7 +986,7 @@
             new CfInvoke(
                 183,
                 factory.createMethod(
-                    factory.createType("Ljava/lang/AssertionError;"),
+                    factory.createType("Ljava/lang/RuntimeException;"),
                     factory.createProto(
                         factory.voidType, factory.stringType, factory.throwableType),
                     factory.createString("<init>")),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
index 520533b..a1a8d29 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
@@ -91,7 +91,7 @@
         (k, v) -> {
           for (DexMethod dexMethod : v) {
             if (dexMethod != null) {
-              registerClassType(dexMethod.getHolderType());
+              registerType(dexMethod.getHolderType());
             }
           }
         });
@@ -159,9 +159,13 @@
                   type.descriptor.withNewPrefix(prefix, k, appInfo.dexItemFactory());
               DexString rewrittenTypeDescriptor =
                   type.descriptor.withNewPrefix(prefix, v, appInfo.dexItemFactory());
+              DexType newKey = appInfo.dexItemFactory().createType(typeDescriptor);
+              assert appInfo.definitionForWithoutExistenceAssert(newKey) == null
+                  : "Trying to rewrite a type "
+                      + newKey
+                      + " with different prefix that already exists.";
               builder.rewriteType(
-                  appInfo.dexItemFactory().createType(typeDescriptor),
-                  appInfo.dexItemFactory().createType(rewrittenTypeDescriptor));
+                  newKey, appInfo.dexItemFactory().createType(rewrittenTypeDescriptor));
             });
     usedPrefix.add(prefix);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
index c0b124b..b251add 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
@@ -67,7 +67,7 @@
     Map<ApiLevelRange, HumanRewritingFlags> libraryFlags =
         convertRewritingFlagMap(legacySpec.getLibraryFlags(), app, origin);
 
-    legacyLibraryFlagHacks(libraryFlags, app, origin);
+    legacyLibraryFlagHacks(humanTopLevelFlags.getIdentifier(), libraryFlags, app, origin);
     reportWarnings(app.options.reporter);
 
     MultiAPILevelHumanDesugaredLibrarySpecification humanSpec =
@@ -94,12 +94,12 @@
     Origin origin = Origin.unknown();
     HumanRewritingFlags humanRewritingFlags =
         convertRewritingFlags(legacySpec.getRewritingFlags(), app, origin);
-    if (app.options.getMinApiLevel().isLessThanOrEqualTo(LEGACY_HACK_LEVEL)
-        && legacySpec.isLibraryCompilation()) {
+    if (legacySpec.isLibraryCompilation()) {
       timing.begin("Legacy hacks");
       HumanRewritingFlags.Builder builder =
           humanRewritingFlags.newBuilder(app.options.reporter, origin);
-      legacyLibraryFlagHacks(app.dexItemFactory(), builder);
+      legacyLibraryFlagHacks(
+          legacySpec.getIdentifier(), app.dexItemFactory(), app.options.getMinApiLevel(), builder);
       humanRewritingFlags = builder.build();
       timing.end();
     }
@@ -128,7 +128,10 @@
   }
 
   private void legacyLibraryFlagHacks(
-      Map<ApiLevelRange, HumanRewritingFlags> libraryFlags, DexApplication app, Origin origin) {
+      String identifier,
+      Map<ApiLevelRange, HumanRewritingFlags> libraryFlags,
+      DexApplication app,
+      Origin origin) {
     ApiLevelRange range = new ApiLevelRange(LEGACY_HACK_LEVEL.getLevel());
     HumanRewritingFlags humanRewritingFlags = libraryFlags.get(range);
     if (humanRewritingFlags == null) {
@@ -137,39 +140,52 @@
     }
     HumanRewritingFlags.Builder builder =
         humanRewritingFlags.newBuilder(app.options.reporter, origin);
-    legacyLibraryFlagHacks(app.dexItemFactory(), builder);
+    legacyLibraryFlagHacks(identifier, app.dexItemFactory(), LEGACY_HACK_LEVEL, builder);
     libraryFlags.put(range, builder.build());
   }
 
   private void legacyLibraryFlagHacks(
-      DexItemFactory itemFactory, HumanRewritingFlags.Builder builder) {
+      String identifier,
+      DexItemFactory itemFactory,
+      AndroidApiLevel apiLevel,
+      HumanRewritingFlags.Builder builder) {
 
-    // TODO(b/177977763): This is only a workaround rewriting invokes of j.u.Arrays.deepEquals0
-    // to j.u.DesugarArrays.deepEquals0.
-    DexString name = itemFactory.createString("deepEquals0");
-    DexProto proto =
-        itemFactory.createProto(
-            itemFactory.booleanType, itemFactory.objectType, itemFactory.objectType);
-    DexMethod source =
-        itemFactory.createMethod(itemFactory.createType(itemFactory.arraysDescriptor), proto, name);
-    DexType target = itemFactory.createType("Ljava/util/DesugarArrays;");
-    builder.retargetMethod(source, target);
+    if (apiLevel.isLessThanOrEqualTo(LEGACY_HACK_LEVEL)) {
+      // TODO(b/177977763): This is only a workaround rewriting invokes of j.u.Arrays.deepEquals0
+      // to j.u.DesugarArrays.deepEquals0.
+      DexString name = itemFactory.createString("deepEquals0");
+      DexProto proto =
+          itemFactory.createProto(
+              itemFactory.booleanType, itemFactory.objectType, itemFactory.objectType);
+      DexMethod source =
+          itemFactory.createMethod(
+              itemFactory.createType(itemFactory.arraysDescriptor), proto, name);
+      DexType target = itemFactory.createType("Ljava/util/DesugarArrays;");
+      builder.retargetMethod(source, target);
 
-    builder.amendLibraryMethod(
-        source,
-        MethodAccessFlags.fromSharedAccessFlags(
-            Constants.ACC_PRIVATE | Constants.ACC_STATIC, false));
+      builder.amendLibraryMethod(
+          source,
+          MethodAccessFlags.fromSharedAccessFlags(
+              Constants.ACC_PRIVATE | Constants.ACC_STATIC, false));
 
-    // TODO(b/181629049): This is only a workaround rewriting invokes of
-    //  j.u.TimeZone.getTimeZone taking a java.time.ZoneId.
-    name = itemFactory.createString("getTimeZone");
-    proto =
-        itemFactory.createProto(
-            itemFactory.createType("Ljava/util/TimeZone;"),
-            itemFactory.createType("Ljava/time/ZoneId;"));
-    source = itemFactory.createMethod(itemFactory.createType("Ljava/util/TimeZone;"), proto, name);
-    target = itemFactory.createType("Ljava/util/DesugarTimeZone;");
-    builder.retargetMethod(source, target);
+      // TODO(b/181629049): This is only a workaround rewriting invokes of
+      //  j.u.TimeZone.getTimeZone taking a java.time.ZoneId.
+      name = itemFactory.createString("getTimeZone");
+      proto =
+          itemFactory.createProto(
+              itemFactory.createType("Ljava/util/TimeZone;"),
+              itemFactory.createType("Ljava/time/ZoneId;"));
+      source =
+          itemFactory.createMethod(itemFactory.createType("Ljava/util/TimeZone;"), proto, name);
+      target = itemFactory.createType("Ljava/util/DesugarTimeZone;");
+      builder.retargetMethod(source, target);
+    }
+    // Required by
+    // https://github.com/google/desugar_jdk_libs/commit/485071cd09a3691549d065ba9e323d07edccf085.
+    if (identifier.contains(":1.2")) {
+      builder.putRewriteDerivedPrefix(
+          "sun.misc.Desugar", "jdk.internal.misc.", "j$.sun.misc.Desugar");
+    }
   }
 
   private Map<ApiLevelRange, HumanRewritingFlags> convertRewritingFlagMap(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionErrorTwoArgsConstructorRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionErrorTwoArgsConstructorRewriter.java
new file mode 100644
index 0000000..06d9d8f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionErrorTwoArgsConstructorRewriter.java
@@ -0,0 +1,109 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize;
+
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.desugar.backports.BackportedMethods;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+
+public class AssertionErrorTwoArgsConstructorRewriter {
+
+  private final AppView<?> appView;
+  private final DexItemFactory dexItemFactory;
+  private final InternalOptions options;
+
+  public AssertionErrorTwoArgsConstructorRewriter(AppView<?> appView) {
+    this.appView = appView;
+    this.options = appView.options();
+    this.dexItemFactory = appView.dexItemFactory();
+  }
+
+  public void rewrite(IRCode code, MethodProcessingContext methodProcessingContext) {
+    if (options.canUseAssertionErrorTwoArgumentConstructor()) {
+      return;
+    }
+
+    ListIterator<BasicBlock> blockIterator = code.listIterator();
+    while (blockIterator.hasNext()) {
+      BasicBlock block = blockIterator.next();
+      InstructionListIterator insnIterator = block.listIterator(code);
+      while (insnIterator.hasNext()) {
+        Instruction current = insnIterator.next();
+        if (current.isInvokeMethod()) {
+          DexMethod invokedMethod = current.asInvokeMethod().getInvokedMethod();
+          if (invokedMethod == dexItemFactory.assertionErrorMethods.initMessageAndCause) {
+            List<Value> inValues = current.inValues();
+            assert inValues.size() == 3; // receiver, message, cause
+
+            Value assertionError =
+                code.createValue(
+                    TypeElement.fromDexType(
+                        dexItemFactory.assertionErrorType,
+                        Nullability.definitelyNotNull(),
+                        appView));
+            Instruction invoke =
+                new InvokeStatic(
+                    createSynthetic(methodProcessingContext).getReference(),
+                    assertionError,
+                    inValues.subList(1, 3));
+            insnIterator.replaceCurrentInstruction(invoke);
+            inValues.get(0).replaceUsers(assertionError);
+            inValues.get(0).definition.removeOrReplaceByDebugLocalRead(code);
+          }
+        }
+      }
+    }
+    assert code.isConsistentSSA(appView);
+  }
+
+  private final List<ProgramMethod> synthesizedMethods = new ArrayList<>();
+
+  public List<ProgramMethod> getSynthesizedMethods() {
+    return synthesizedMethods;
+  }
+
+  private ProgramMethod createSynthetic(MethodProcessingContext methodProcessingContext) {
+    DexItemFactory factory = appView.dexItemFactory();
+    DexProto proto =
+        factory.createProto(factory.assertionErrorType, factory.stringType, factory.throwableType);
+    ProgramMethod method =
+        appView
+            .getSyntheticItems()
+            .createMethod(
+                kinds -> kinds.BACKPORT,
+                methodProcessingContext.createUniqueContext(),
+                appView,
+                builder ->
+                    builder
+                        .setApiLevelForCode(appView.computedMinApiLevel())
+                        .setProto(proto)
+                        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                        .setCode(
+                            methodSig ->
+                                BackportedMethods.AssertionErrorMethods_createAssertionError(
+                                    factory, methodSig)));
+    synchronized (synthesizedMethods) {
+      synthesizedMethods.add(method);
+    }
+    return method;
+  }
+}
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 36b96dc..4d31397 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
@@ -138,7 +138,6 @@
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collection;
 import java.util.Collections;
@@ -3424,55 +3423,6 @@
     assert code.isConsistentSSA(appView);
   }
 
-  public void rewriteAssertionErrorTwoArgumentConstructor(IRCode code, InternalOptions options) {
-    if (options.canUseAssertionErrorTwoArgumentConstructor()) {
-      return;
-    }
-
-    ListIterator<BasicBlock> blockIterator = code.listIterator();
-    while (blockIterator.hasNext()) {
-      BasicBlock block = blockIterator.next();
-      InstructionListIterator insnIterator = block.listIterator(code);
-      while (insnIterator.hasNext()) {
-        Instruction current = insnIterator.next();
-        if (current.isInvokeMethod()) {
-          DexMethod invokedMethod = current.asInvokeMethod().getInvokedMethod();
-          if (invokedMethod == dexItemFactory.assertionErrorMethods.initMessageAndCause) {
-            // Rewrite calls to new AssertionError(message, cause) to new AssertionError(message)
-            // and then initCause(cause).
-            List<Value> inValues = current.inValues();
-            assert inValues.size() == 3; // receiver, message, cause
-
-            // Remove cause from the constructor call
-            List<Value> newInitInValues = inValues.subList(0, 2);
-            insnIterator.replaceCurrentInstruction(
-                new InvokeDirect(
-                    dexItemFactory.assertionErrorMethods.initMessage, null, newInitInValues));
-
-            // On API 15 and older we cannot use initCause because of a bug in AssertionError.
-            if (options.canInitCauseAfterAssertionErrorObjectConstructor()) {
-              // Add a call to Throwable.initCause(cause)
-              if (block.hasCatchHandlers()) {
-                insnIterator = insnIterator.split(code, blockIterator).listIterator(code);
-              }
-              List<Value> initCauseArguments = Arrays.asList(inValues.get(0), inValues.get(2));
-              InvokeVirtual initCause =
-                  new InvokeVirtual(
-                      dexItemFactory.throwableMethods.initCause,
-                      code.createValue(
-                          TypeElement.fromDexType(
-                              dexItemFactory.throwableType, maybeNull(), appView)),
-                      initCauseArguments);
-              initCause.setPosition(current.getPosition());
-              insnIterator.add(initCause);
-            }
-          }
-        }
-      }
-    }
-    assert code.isConsistentSSA(appView);
-  }
-
   /**
    * Remove moves that are not actually used by instructions in exiting paths. These moves can arise
    * due to debug local info needing a particular value and the live-interval for it then moves it
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index 5275a80..eb7671a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -139,11 +139,9 @@
 
   private Set<BasicBlock> computeDirectAndIndirectCatchHandlerBlocks() {
     WorkList<BasicBlock> catchHandlerBlocks = WorkList.newIdentityWorkList();
-    for (BasicBlock block : code.getBlocks()) {
-      if (block.entry().isMoveException()) {
-        catchHandlerBlocks.addIfNotSeen(block);
-      }
-    }
+    code.getBlocks()
+        .forEach(
+            block -> catchHandlerBlocks.addIfNotSeen(block.getCatchHandlers().getAllTargets()));
     while (catchHandlerBlocks.hasNext()) {
       BasicBlock block = catchHandlerBlocks.next();
       catchHandlerBlocks.addIfNotSeen(block.getSuccessors());
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 ac4c0ef..69d51be 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
@@ -446,26 +446,6 @@
   }
 
   @Override
-  public boolean allowInliningOfInvokeInInlinee(
-      InlineAction action,
-      int inliningDepth,
-      WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
-    assert inliningDepth > 0;
-
-    if (action.reason.mustBeInlined()) {
-      return true;
-    }
-
-    int threshold = inlinerOptions.applyInliningToInlineeMaxDepth;
-    if (inliningDepth <= threshold) {
-      return true;
-    }
-
-    whyAreYouNotInliningReporter.reportWillExceedMaxInliningDepth(inliningDepth, threshold);
-    return false;
-  }
-
-  @Override
   public boolean canInlineInstanceInitializer(
       IRCode code,
       InvokeDirect invoke,
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 be64b57..e37d406 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
@@ -101,15 +101,6 @@
   }
 
   @Override
-  public boolean allowInliningOfInvokeInInlinee(
-      InlineAction action,
-      int inliningDepth,
-      WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
-    // The purpose of force inlining is generally to inline a given invoke-instruction in the IR.
-    return false;
-  }
-
-  @Override
   public boolean canInlineInstanceInitializer(
       IRCode code,
       InvokeDirect invoke,
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 9a38d14..592b645 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
@@ -1014,13 +1014,6 @@
             continue;
           }
 
-          if (!inlineeStack.isEmpty()
-              && !strategy.allowInliningOfInvokeInInlinee(
-                  action, inlineeStack.size(), whyAreYouNotInliningReporter)) {
-            assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
-            continue;
-          }
-
           if (!strategy.stillHasBudget(action, whyAreYouNotInliningReporter)) {
             assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
             continue;
@@ -1084,17 +1077,16 @@
 
           context.getDefinition().copyMetadata(appView, singleTargetMethod);
 
-          if (inlineeMayHaveInvokeMethod && options.applyInliningToInlinee) {
-            if (inlineeStack.size() + 1 > options.applyInliningToInlineeMaxDepth
-                && appView.appInfo().hasNoAlwaysInlineMethods()) {
-              continue;
+          if (inlineeMayHaveInvokeMethod) {
+            int inliningDepth = inlineeStack.size() + 1;
+            if (options.shouldApplyInliningToInlinee(appView, singleTarget, inliningDepth)) {
+              // Record that we will be inside the inlinee until the next block.
+              BasicBlock inlineeEnd = IteratorUtils.peekNext(blockIterator);
+              inlineeStack.push(inlineeEnd);
+              // Move the cursor back to where the first inlinee block was added.
+              IteratorUtils.previousUntil(blockIterator, previous -> previous == block);
+              blockIterator.next();
             }
-            // Record that we will be inside the inlinee until the next block.
-            BasicBlock inlineeEnd = IteratorUtils.peekNext(blockIterator);
-            inlineeStack.push(inlineeEnd);
-            // Move the cursor back to where the first inlinee block was added.
-            IteratorUtils.previousUntil(blockIterator, previous -> previous == block);
-            blockIterator.next();
           }
         } else if (current.isAssume()) {
           assumeRemover.removeIfMarked(current.asAssume(), iterator);
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 564fcf8..b76544b 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
@@ -24,11 +24,6 @@
 
   AppView<AppInfoWithLiveness> appView();
 
-  boolean allowInliningOfInvokeInInlinee(
-      InlineAction action,
-      int inliningDepth,
-      WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
-
   boolean canInlineInstanceInitializer(
       IRCode code,
       InvokeDirect invoke,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InstanceInitializerOutliner.java b/src/main/java/com/android/tools/r8/ir/optimize/InstanceInitializerOutliner.java
new file mode 100644
index 0000000..9aa717b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InstanceInitializerOutliner.java
@@ -0,0 +1,240 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize;
+
+import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+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.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
+import com.android.tools.r8.ir.synthetic.NewInstanceSourceCode;
+import com.android.tools.r8.shaking.ComputeApiLevelUseRegistry;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The InstanceInitializerOutliner will outline instance initializers and their NewInstance source.
+ * Unlike the ApiInvokeOutlinerDesugaring that works on CF, this works on IR to properly replace the
+ * users of the NewInstance call.
+ */
+public class InstanceInitializerOutliner {
+
+  private final AppView<?> appView;
+  private final DexItemFactory factory;
+
+  private final List<ProgramMethod> synthesizedMethods = new ArrayList<>();
+
+  public InstanceInitializerOutliner(AppView<?> appView) {
+    this.appView = appView;
+    this.factory = appView.dexItemFactory();
+  }
+
+  public List<ProgramMethod> getSynthesizedMethods() {
+    return synthesizedMethods;
+  }
+
+  public void rewriteInstanceInitializers(
+      IRCode code, ProgramMethod context, MethodProcessingContext methodProcessingContext) {
+    // Do not outline from already synthesized methods.
+    if (context.getDefinition().isD8R8Synthesized()) {
+      return;
+    }
+    Map<NewInstance, Value> rewrittenNewInstances = new IdentityHashMap<>();
+    ComputedApiLevel minApiLevel = appView.computedMinApiLevel();
+    InstructionListIterator iterator = code.instructionListIterator();
+    // Scan over the code to find <init> calls that needs to be outlined.
+    while (iterator.hasNext()) {
+      Instruction instruction = iterator.next();
+      InvokeDirect invokeDirect = instruction.asInvokeDirect();
+      if (invokeDirect == null) {
+        continue;
+      }
+      DexMethod invokedConstructor = invokeDirect.getInvokedMethod();
+      if (!invokedConstructor.isInstanceInitializer(factory)) {
+        continue;
+      }
+      Value firstOperand = invokeDirect.getFirstOperand();
+      if (firstOperand.isPhi()) {
+        continue;
+      }
+      NewInstance newInstance = firstOperand.getDefinition().asNewInstance();
+      if (newInstance == null) {
+        // We could not find a new instance call associated with the init, this is probably a
+        // constructor call to the super class.
+        continue;
+      }
+      ComputedApiLevel apiReferenceLevel =
+          appView
+              .apiLevelCompute()
+              .computeApiLevelForLibraryReference(invokedConstructor, minApiLevel);
+      if (minApiLevel.isGreaterThanOrEqualTo(apiReferenceLevel)) {
+        continue;
+      }
+      DexEncodedMethod synthesizedInstanceInitializer =
+          createSynthesizedInstanceInitializer(
+              invokeDirect.getInvokedMethod(), apiReferenceLevel, methodProcessingContext);
+      List<Value> arguments = instruction.inValues();
+      InvokeStatic outlinedMethodInvoke =
+          InvokeStatic.builder()
+              .setMethod(synthesizedInstanceInitializer.getReference())
+              .setPosition(instruction)
+              .setFreshOutValue(code, newInstance.getOutType())
+              .setArguments(arguments.subList(1, arguments.size()))
+              .build();
+      iterator.replaceCurrentInstruction(outlinedMethodInvoke);
+      rewrittenNewInstances.put(newInstance, outlinedMethodInvoke.outValue());
+    }
+    if (rewrittenNewInstances.isEmpty()) {
+      return;
+    }
+    // Scan over NewInstance calls that needs to be outlined. We insert a call to a synthetic method
+    // with a NewInstance to preserve class-init semantics.
+    // TODO(b/244284945): If we know that arguments to an init cannot change class initializer
+    //  semantics we can avoid inserting the NewInstance outline.
+    iterator = code.instructionListIterator();
+    Set<Value> newOutValues = Sets.newIdentityHashSet();
+    while (iterator.hasNext()) {
+      Instruction instruction = iterator.next();
+      if (!instruction.isNewInstance()) {
+        continue;
+      }
+      NewInstance newInstance = instruction.asNewInstance();
+      Value newOutlineInstanceValue = rewrittenNewInstances.get(newInstance);
+      if (newOutlineInstanceValue == null) {
+        continue;
+      }
+      ComputedApiLevel classApiLevel =
+          appView
+              .apiLevelCompute()
+              .computeApiLevelForLibraryReference(newInstance.getType(), minApiLevel);
+      assert classApiLevel.isKnownApiLevel();
+      DexEncodedMethod synthesizedNewInstance =
+          createSynthesizedNewInstance(
+              newInstance.getType(), classApiLevel, methodProcessingContext);
+      InvokeStatic outlinedStaticInit =
+          InvokeStatic.builder()
+              .setMethod(synthesizedNewInstance.getReference())
+              .setPosition(instruction)
+              .build();
+      newInstance.outValue().replaceUsers(newOutlineInstanceValue);
+      newOutValues.add(newOutlineInstanceValue);
+      iterator.replaceCurrentInstruction(outlinedStaticInit);
+    }
+    // We are changing a NewInstance to a method call where we loose that the type is not null.
+    assert !newOutValues.isEmpty();
+    new TypeAnalysis(appView).widening(newOutValues);
+
+    // Outlining of instance initializers will in most cases change the api level of the context
+    // since all other soft verification issues has been outlined. To ensure that we do not inline
+    // the outline again in R8 - but allow inlining of other calls to min api level methods, we have
+    // to recompute the api level.
+    recomputeApiLevel(context, code);
+  }
+
+  private void recomputeApiLevel(ProgramMethod context, IRCode code) {
+    DexEncodedMethod definition = context.getDefinition();
+    if (!definition.getApiLevelForCode().isKnownApiLevel()) {
+      // This is either D8 or the api level is unknown.
+      return;
+    }
+    ComputeApiLevelUseRegistry registry =
+        new ComputeApiLevelUseRegistry(appView, context, appView.apiLevelCompute());
+    code.registerCodeReferences(context, registry);
+    ComputedApiLevel maxApiReferenceLevel = registry.getMaxApiReferenceLevel();
+    assert maxApiReferenceLevel.isKnownApiLevel();
+    definition.setApiLevelForCode(maxApiReferenceLevel);
+  }
+
+  private DexEncodedMethod createSynthesizedNewInstance(
+      DexType targetType,
+      ComputedApiLevel computedApiLevel,
+      MethodProcessingContext methodProcessingContext) {
+    DexProto proto = appView.dexItemFactory().createProto(factory.voidType);
+    ProgramMethod method =
+        appView
+            .getSyntheticItems()
+            .createMethod(
+                kinds -> kinds.API_MODEL_OUTLINE,
+                methodProcessingContext.createUniqueContext(),
+                appView,
+                builder ->
+                    builder
+                        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                        .setProto(proto)
+                        .setApiLevelForDefinition(appView.computedMinApiLevel())
+                        .setApiLevelForCode(computedApiLevel)
+                        .setCode(
+                            m ->
+                                NewInstanceSourceCode.create(appView, m.getHolderType(), targetType)
+                                    .generateCfCode()));
+    synchronized (synthesizedMethods) {
+      synthesizedMethods.add(method);
+    }
+    return method.getDefinition();
+  }
+
+  private DexEncodedMethod createSynthesizedInstanceInitializer(
+      DexMethod targetMethod,
+      ComputedApiLevel computedApiLevel,
+      MethodProcessingContext methodProcessingContext) {
+    DexProto proto =
+        appView
+            .dexItemFactory()
+            .createProto(targetMethod.getHolderType(), targetMethod.getParameters());
+    ProgramMethod method =
+        appView
+            .getSyntheticItems()
+            .createMethod(
+                kinds -> kinds.API_MODEL_OUTLINE,
+                methodProcessingContext.createUniqueContext(),
+                appView,
+                builder ->
+                    builder
+                        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                        .setProto(proto)
+                        .setApiLevelForDefinition(appView.computedMinApiLevel())
+                        .setApiLevelForCode(computedApiLevel)
+                        .setCode(
+                            m ->
+                                ForwardMethodBuilder.builder(appView.dexItemFactory())
+                                    .setConstructorTargetWithNewInstance(targetMethod)
+                                    .setStaticSource(m)
+                                    .build()));
+
+    synchronized (synthesizedMethods) {
+      synthesizedMethods.add(method);
+      ClassTypeElement exactType =
+          targetMethod
+              .getHolderType()
+              .toTypeElement(appView, Nullability.definitelyNotNull())
+              .asClassType();
+      OptimizationFeedback.getSimpleFeedback()
+          .setDynamicReturnType(method, appView, DynamicType.createExact(exactType));
+    }
+    return method.getDefinition();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index e19e0b6..44eed32 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -125,7 +125,7 @@
         localUtilityClass.getDefinition().setDirectMethods(localUtilityMethods);
         localUtilityClass.getDefinition().setStaticFields(localUtilityFields);
       } else {
-        clazz.getMethodCollection().replaceMethods(method -> fixupEncodedMethod(clazz, method));
+        clazz.getMethodCollection().replaceMethods(this::fixupEncodedMethod);
         fixupFields(clazz.staticFields(), clazz::setStaticField);
         fixupFields(clazz.instanceFields(), clazz::setInstanceField);
       }
@@ -561,7 +561,7 @@
             && !field.getDefinition().getOptimizationInfo().isDead());
   }
 
-  private DexEncodedMethod fixupEncodedMethod(DexProgramClass holder, DexEncodedMethod method) {
+  private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod method) {
     DexProto oldProto = method.getProto();
     DexProto newProto = fixupProto(oldProto);
     if (newProto == method.getProto()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java
index 12c5b37..0044499 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java
@@ -10,14 +10,13 @@
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
 import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
-import com.android.tools.r8.ir.optimize.info.bridge.VirtualBridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.BitSet;
 
 public abstract class MethodOptimizationInfoFixer {
 
-  public abstract BridgeInfo fixupBridgeInfo(VirtualBridgeInfo bridgeInfo);
+  public abstract BridgeInfo fixupBridgeInfo(BridgeInfo bridgeInfo);
 
   public abstract CallSiteOptimizationInfo fixupCallSiteOptimizationInfo(
       ConcreteCallSiteOptimizationInfo callSiteOptimizationInfo);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index 3491800..2e760d1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -396,8 +396,7 @@
 
   public MutableMethodOptimizationInfo fixupBridgeInfo(MethodOptimizationInfoFixer fixer) {
     if (bridgeInfo != null) {
-      assert bridgeInfo.isVirtualBridgeInfo();
-      bridgeInfo = fixer.fixupBridgeInfo(bridgeInfo.asVirtualBridgeInfo());
+      bridgeInfo = fixer.fixupBridgeInfo(bridgeInfo);
     }
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java
index 9eeb579..be806b4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.ir.code.Opcodes.ARGUMENT;
 import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
 import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
 import static com.android.tools.r8.ir.code.Opcodes.RETURN;
 
@@ -14,7 +15,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -23,8 +24,7 @@
 
   /** Returns a {@link BridgeInfo} object describing this method if it is recognized as a bridge. */
   public static BridgeInfo analyzeMethod(DexEncodedMethod method, IRCode code) {
-    // TODO(b/154263783): Consider computing BridgeInfo also for non-declared bridges.
-    if (!method.isBridge() || code.blocks.size() > 1) {
+    if (code.blocks.size() > 1) {
       return failure();
     }
 
@@ -32,7 +32,7 @@
     // followed by a (possibly empty) sequence of CheckCast instructions, followed by a single
     // InvokeMethod instruction, followed by an optional CheckCast instruction, followed by a Return
     // instruction.
-    InvokeVirtual uniqueInvoke = null;
+    InvokeMethodWithReceiver uniqueInvoke = null;
     CheckCast uniqueReturnCast = null;
     for (Instruction instruction : code.entryBlock().getInstructions()) {
       switch (instruction.opcode()) {
@@ -59,13 +59,14 @@
             break;
           }
 
+        case INVOKE_DIRECT:
         case INVOKE_VIRTUAL:
           {
             if (uniqueInvoke != null) {
               return failure();
             }
-            InvokeVirtual invoke = instruction.asInvokeVirtual();
-            if (!analyzeInvokeVirtual(invoke)) {
+            InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
+            if (!analyzeInvoke(invoke)) {
               return failure();
             }
             // Record that we have seen the single invoke instruction.
@@ -85,7 +86,10 @@
     }
 
     assert uniqueInvoke != null;
-    return new VirtualBridgeInfo(uniqueInvoke.getInvokedMethod());
+    assert uniqueInvoke.isInvokeDirect() || uniqueInvoke.isInvokeVirtual();
+    return uniqueInvoke.isInvokeDirect()
+        ? new DirectBridgeInfo(uniqueInvoke.getInvokedMethod())
+        : new VirtualBridgeInfo(uniqueInvoke.getInvokedMethod());
   }
 
   private static boolean analyzeCheckCast(
@@ -148,11 +152,12 @@
         && castValue.singleUniqueUser().isReturn();
   }
 
-  private static boolean analyzeInvokeVirtual(InvokeVirtual invoke) {
+  private static boolean analyzeInvoke(InvokeMethodWithReceiver invoke) {
     // All of the forwarded arguments of the enclosing method must be in the same argument position.
     for (int argumentIndex = 0; argumentIndex < invoke.arguments().size(); argumentIndex++) {
       Value argument = invoke.getArgument(argumentIndex);
-      if (argument.isArgument() && argumentIndex != argument.definition.asArgument().getIndex()) {
+      if (argument.isArgument()
+          && argumentIndex != argument.getDefinition().asArgument().getIndex()) {
         return false;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
index 2683d2f..3848c7e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
@@ -9,6 +9,14 @@
  */
 public abstract class BridgeInfo {
 
+  public boolean isDirectBridgeInfo() {
+    return false;
+  }
+
+  public DirectBridgeInfo asDirectBridgeInfo() {
+    return null;
+  }
+
   public boolean isVirtualBridgeInfo() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/DirectBridgeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/DirectBridgeInfo.java
new file mode 100644
index 0000000..3579edf
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/DirectBridgeInfo.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2022, 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.info.bridge;
+
+import com.android.tools.r8.graph.DexMethod;
+
+/**
+ * Optimization info computed for bridge methods that use an invoke-direct instruction.
+ *
+ * <p>If the method is {@code String A.m(Object)} and {@link #invokedMethod} is {@code Object
+ * B.m(String)}, then the bridge is implemented as:
+ *
+ * <pre>
+ *   java.lang.String A.m(java.lang.Object o) {
+ *     v0 <- Argument
+ *     v1 <- CheckCast v0, java.lang.String
+ *     v2 <- InvokeDirect { v0, v1 }, java.lang.Object B.m(java.lang.String)
+ *     v3 <- CheckCast v2, java.lang.String
+ *     Return v3
+ *   }
+ * </pre>
+ *
+ * <p>This currently does not allow any permutation of the argument order, and it also does not
+ * allow constants to be passed as arguments.
+ */
+public class DirectBridgeInfo extends BridgeInfo {
+
+  // The targeted method.
+  private final DexMethod invokedMethod;
+
+  public DirectBridgeInfo(DexMethod invokedMethod) {
+    this.invokedMethod = invokedMethod;
+  }
+
+  public DexMethod getInvokedMethod() {
+    return invokedMethod;
+  }
+
+  @Override
+  public boolean isDirectBridgeInfo() {
+    return true;
+  }
+
+  @Override
+  public DirectBridgeInfo asDirectBridgeInfo() {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ExceptionThrowingSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/ExceptionThrowingSourceCode.java
deleted file mode 100644
index 080b519..0000000
--- a/src/main/java/com/android/tools/r8/ir/synthetic/ExceptionThrowingSourceCode.java
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.synthetic;
-
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.conversion.IRBuilder;
-import java.util.Collections;
-
-// Source code representing simple forwarding method.
-public final class ExceptionThrowingSourceCode extends SyntheticSourceCode {
-
-  private static final int register = 0;
-  private final DexType exceptionType;
-
-  public ExceptionThrowingSourceCode(
-      DexType receiver, DexMethod method, Position callerPosition, DexType exceptionType) {
-    super(receiver, method, callerPosition);
-    this.exceptionType = exceptionType;
-  }
-
-  @Override
-  protected void prepareInstructions() {
-    add(builder -> build(builder, exceptionType));
-  }
-
-  public static void build(IRBuilder builder, DexType exceptionType) {
-    DexItemFactory factory = builder.appView.dexItemFactory();
-    DexProto initProto = factory.createProto(factory.voidType);
-    DexMethod initMethod =
-        factory.createMethod(exceptionType, initProto, factory.constructorMethodName);
-    builder.addNewInstance(register, exceptionType);
-    builder.addInvoke(
-        Type.DIRECT,
-        initMethod,
-        initMethod.proto,
-        Collections.singletonList(ValueType.OBJECT),
-        Collections.singletonList(register),
-        false /* isInterface */);
-    builder.addThrow(register);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
index 90065e6..8d50c17 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
@@ -142,7 +142,7 @@
     return this;
   }
 
-  public ForwardMethodBuilder setConstructorTarget(DexMethod method, DexItemFactory factory) {
+  public ForwardMethodBuilder setConstructorTargetWithNewInstance(DexMethod method) {
     assert method.isInstanceInitializer(factory);
     targetMethod = method;
     isConstructorDelegate = true;
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/NewInstanceSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/NewInstanceSourceCode.java
new file mode 100644
index 0000000..02b32bc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/NewInstanceSourceCode.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2022, 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.synthetic;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexType;
+import java.util.ArrayList;
+import java.util.List;
+
+// Source code representing a simple call to NewInstance.
+public final class NewInstanceSourceCode extends SyntheticCfCodeProvider {
+
+  private final DexType newInstanceType;
+
+  private NewInstanceSourceCode(AppView<?> appView, DexType holder, DexType newInstanceType) {
+    super(appView, holder);
+    this.newInstanceType = newInstanceType;
+  }
+
+  public static NewInstanceSourceCode create(
+      AppView<?> appView, DexType holder, DexType newInstanceType) {
+    return new NewInstanceSourceCode(appView, holder, newInstanceType);
+  }
+
+  @Override
+  public CfCode generateCfCode() {
+    List<CfInstruction> instructions = new ArrayList<>();
+    instructions.add(new CfNew(newInstanceType));
+    instructions.add(new CfReturnVoid());
+    return standardCfCodeFromInstructions(instructions);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 6586750..461dc2a 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -90,7 +90,7 @@
   private final DexApplication application;
   private final AppView<?> appView;
   private final InternalOptions options;
-  private final Marker marker;
+  private final Optional<Marker> marker;
   private final Predicate<DexType> isTypeMissing;
 
   private static final CfVersion MIN_VERSION_FOR_COMPILER_GENERATED_CODE = CfVersion.V1_6;
@@ -99,8 +99,7 @@
     this.application = appView.appInfo().app();
     this.appView = appView;
     this.options = appView.options();
-    assert marker != null;
-    this.marker = marker;
+    this.marker = Optional.ofNullable(marker);
     this.isTypeMissing =
         PredicateUtils.isNull(appView.appInfo()::definitionForWithoutExistenceAssert);
   }
@@ -137,6 +136,7 @@
   private void writeApplication(AndroidApp inputApp, ClassFileConsumer consumer) {
     ProguardMapId proguardMapId = null;
     if (options.proguardMapConsumer != null) {
+      assert marker.isPresent();
       proguardMapId =
           runAndWriteMap(
               inputApp,
@@ -144,10 +144,9 @@
               application.timing,
               OriginalSourceFiles.fromClasses(),
               DebugRepresentation.none(options));
-      marker.setPgMapId(proguardMapId.getId());
+      marker.get().setPgMapId(proguardMapId.getId());
     }
-    Optional<String> markerString =
-        includeMarker(marker) ? Optional.of(marker.toString()) : Optional.empty();
+    Optional<String> markerString = marker.filter(this::includeMarker).map(Marker::toString);
     SourceFileEnvironment sourceFileEnvironment = null;
     if (options.sourceFileProvider != null) {
       sourceFileEnvironment = ApplicationWriter.createSourceFileEnvironment(proguardMapId);
diff --git a/src/main/java/com/android/tools/r8/lightir/IR2LIRConverter.java b/src/main/java/com/android/tools/r8/lightir/IR2LIRConverter.java
index e91fd8a..4e148ee 100644
--- a/src/main/java/com/android/tools/r8/lightir/IR2LIRConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/IR2LIRConverter.java
@@ -3,43 +3,140 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.lightir;
 
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.lightir.LIRBuilder.BlockIndexGetter;
+import com.android.tools.r8.utils.ListUtils;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap;
+import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
 
 public class IR2LIRConverter {
 
   private IR2LIRConverter() {}
 
-  public static LIRCode translate(IRCode irCode) {
+  public static LIRCode translate(IRCode irCode, DexItemFactory factory) {
+    irCode.traceBlocks();
+    Reference2IntMap<BasicBlock> blocks = new Reference2IntOpenHashMap<>();
     Reference2IntMap<Value> values = new Reference2IntOpenHashMap<>();
-    int index = 0;
-    for (Instruction instruction : irCode.instructions()) {
-      if (instruction.hasOutValue()) {
-        values.put(instruction.outValue(), index);
+    int instructionIndex = 0;
+    int valueIndex = 0;
+    for (BasicBlock block : irCode.blocks) {
+      blocks.put(block, instructionIndex);
+      for (Phi phi : block.getPhis()) {
+        values.put(phi, valueIndex);
+        valueIndex++;
+        instructionIndex++;
       }
-      index++;
+      for (Instruction instruction : block.getInstructions()) {
+        if (instruction.hasOutValue()) {
+          values.put(instruction.outValue(), valueIndex);
+        }
+        valueIndex++;
+        if (!instruction.isArgument()) {
+          instructionIndex++;
+        }
+      }
     }
-    LIRBuilder<Value> builder =
-        new LIRBuilder<Value>(irCode.context().getReference(), values::getInt)
+    LIRBuilder<Value, BasicBlock> builder =
+        new LIRBuilder<Value, BasicBlock>(
+                irCode.context().getReference(), values::getInt, blocks::getInt, factory)
             .setMetadata(irCode.metadata());
     BasicBlockIterator blockIt = irCode.listIterator();
     while (blockIt.hasNext()) {
       BasicBlock block = blockIt.next();
-      // TODO(b/225838009): Support control flow.
-      assert !block.hasPhis();
+      if (block.hasPhis()) {
+        // The block order of the predecessors may change, since the LIR does not encode the
+        // direct links, the block order is used to determine predecessor order.
+        int[] permutation = computePermutation(block.getPredecessors(), blocks::getInt);
+        Value[] operands = new Value[block.getPredecessors().size()];
+        for (Phi phi : block.getPhis()) {
+          permuteOperands(phi.getOperands(), permutation, operands);
+          builder.addPhi(phi.getType(), Arrays.asList(operands));
+        }
+      }
+      if (block.hasCatchHandlers()) {
+        CatchHandlers<BasicBlock> handlers = block.getCatchHandlers();
+        builder.addTryCatchHanders(
+            blocks.getInt(block),
+            new CatchHandlers<>(
+                handlers.getGuards(), ListUtils.map(handlers.getAllTargets(), blocks::getInt)));
+      }
       InstructionIterator it = block.iterator();
       while (it.hasNext()) {
         Instruction instruction = it.next();
         builder.setCurrentPosition(instruction.getPosition());
-        instruction.buildLIR(builder);
+        if (instruction.isGoto()) {
+          BasicBlock nextBlock = blockIt.peekNext();
+          if (instruction.asGoto().getTarget() == nextBlock) {
+            builder.addFallthrough();
+          } else {
+            instruction.buildLIR(builder);
+          }
+        } else {
+          instruction.buildLIR(builder);
+        }
       }
     }
     return builder.build();
   }
+
+  private static void permuteOperands(List<Value> operands, int[] permutation, Value[] output) {
+    for (int i = 0; i < operands.size(); i++) {
+      Value operand = operands.get(i);
+      output[permutation[i]] = operand;
+    }
+  }
+
+  private static int[] computePermutation(
+      List<BasicBlock> originalPredecessors, BlockIndexGetter<BasicBlock> blockIndexGetter) {
+    int predecessorCount = originalPredecessors.size();
+    // The final predecessor list is sorted by block order.
+    List<BasicBlock> sortedPredecessors = new ArrayList<>(originalPredecessors);
+    sortedPredecessors.sort(Comparator.comparingInt(blockIndexGetter::getBlockIndex));
+    // Since predecessors are not unique, build a map from each unique block to its set of indices.
+    Reference2ReferenceMap<BasicBlock, IntList> mapping =
+        new Reference2ReferenceOpenHashMap<>(predecessorCount);
+    for (int originalIndex = 0; originalIndex < predecessorCount; originalIndex++) {
+      BasicBlock predecessor = originalPredecessors.get(originalIndex);
+      mapping.computeIfAbsent(predecessor, k -> new IntArrayList(1)).add(originalIndex);
+    }
+    // Assign an original index to each sorted index.
+    int[] permutation = new int[predecessorCount];
+    for (int sortedIndex = 0; sortedIndex < predecessorCount; ) {
+      BasicBlock predecessor = sortedPredecessors.get(sortedIndex);
+      IntList originalIndices = mapping.get(predecessor);
+      assert verifySameBlock(sortedPredecessors, sortedIndex, originalIndices.size());
+      for (int originalIndex : originalIndices) {
+        permutation[originalIndex] = sortedIndex++;
+      }
+    }
+    return permutation;
+  }
+
+  private static boolean verifySameBlock(List<BasicBlock> predecessors, int startIndex, int size) {
+    if (size == 1) {
+      return true;
+    }
+    BasicBlock block = predecessors.get(startIndex);
+    for (int i = startIndex + 1; i < startIndex + size; i++) {
+      BasicBlock other = predecessors.get(i);
+      assert block == other;
+    }
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/lightir/LIR2IRConverter.java b/src/main/java/com/android/tools/r8/lightir/LIR2IRConverter.java
index 75a372b..056129b 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIR2IRConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIR2IRConverter.java
@@ -12,15 +12,25 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.ArrayLength;
 import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.DebugPosition;
+import com.android.tools.r8.ir.code.Div;
+import com.android.tools.r8.ir.code.Goto;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.If.Type;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.MoveException;
 import com.android.tools.r8.ir.code.NumberGenerator;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.Return;
@@ -28,6 +38,10 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.lightir.LIRCode.PositionEntry;
+import com.android.tools.r8.utils.ListUtils;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
 import java.util.ArrayList;
 import java.util.LinkedList;
@@ -50,13 +64,15 @@
    */
   private static class Parser extends LIRParsedInstructionCallback {
 
+    private static final int ENTRY_BLOCK_INDEX = -1;
+
     private final AppView<?> appView;
     private final LIRCode code;
     private final NumberGenerator valueNumberGenerator = new NumberGenerator();
     private final NumberGenerator basicBlockNumberGenerator = new NumberGenerator();
 
     private final Value[] values;
-    private final LinkedList<BasicBlock> blocks = new LinkedList<>();
+    private final Int2ReferenceMap<BasicBlock> blocks = new Int2ReferenceOpenHashMap<>();
 
     private BasicBlock currentBlock = null;
     private int nextInstructionIndex = 0;
@@ -76,9 +92,30 @@
       currentPosition = SyntheticPosition.builder().setLine(0).setMethod(method).build();
     }
 
+    private void closeCurrentBlock() {
+      currentBlock = null;
+    }
+
+    private void ensureCurrentBlock() {
+      // Control instructions must close the block, thus the current block is null iff the
+      // instruction denotes a new block.
+      if (currentBlock == null) {
+        currentBlock = blocks.computeIfAbsent(nextInstructionIndex, k -> new BasicBlock());
+        CatchHandlers<Integer> handlers =
+            code.getTryCatchTable().getHandlersForBlock(nextInstructionIndex);
+        if (handlers != null) {
+          List<BasicBlock> targets = ListUtils.map(handlers.getAllTargets(), this::getBasicBlock);
+          targets.forEach(currentBlock::link);
+          currentBlock.linkCatchSuccessors(handlers.getGuards(), targets);
+        }
+      } else {
+        assert !blocks.containsKey(nextInstructionIndex);
+      }
+    }
+
     private void ensureCurrentPosition() {
       if (nextPositionEntry != null
-          && nextPositionEntry.fromInstructionIndex < nextInstructionIndex) {
+          && nextPositionEntry.fromInstructionIndex <= nextInstructionIndex) {
         currentPosition = nextPositionEntry.position;
         advanceNextPositionEntry();
       }
@@ -92,28 +129,35 @@
     }
 
     public void parseArguments(ProgramMethod method) {
-      currentBlock = new BasicBlock();
-      currentBlock.setNumber(basicBlockNumberGenerator.next());
+      currentBlock = getBasicBlock(ENTRY_BLOCK_INDEX);
       boolean hasReceiverArgument = !method.getDefinition().isStatic();
       assert code.getArgumentCount()
           == method.getParameters().size() + (hasReceiverArgument ? 1 : 0);
       if (hasReceiverArgument) {
         addThisArgument(method.getHolderType());
       }
-      method.getParameters().forEach(this::addArgument);
+      int index = hasReceiverArgument ? 1 : 0;
+      for (DexType parameter : method.getParameters()) {
+        addArgument(parameter, index++);
+      }
       // Set up position state after adding arguments.
       advanceNextPositionEntry();
     }
 
     public IRCode getIRCode(ProgramMethod method) {
-      // TODO(b/225838009): Support control flow.
-      currentBlock.setFilled();
-      blocks.add(currentBlock);
+      LinkedList<BasicBlock> blockList = new LinkedList<>();
+      IntList blockIndices = new IntArrayList(blocks.keySet());
+      blockIndices.sort(Integer::compare);
+      for (int i = 0; i < blockIndices.size(); i++) {
+        BasicBlock block = blocks.get(blockIndices.getInt(i));
+        block.setFilled();
+        blockList.add(block);
+      }
       return new IRCode(
           appView.options(),
           method,
           Position.syntheticNone(),
-          blocks,
+          blockList,
           valueNumberGenerator,
           basicBlockNumberGenerator,
           code.getMetadata(),
@@ -121,7 +165,17 @@
           new MutableMethodConversionOptions(appView.options()));
     }
 
-    public Value getSsaValue(int index) {
+    public BasicBlock getBasicBlock(int instructionIndex) {
+      return blocks.computeIfAbsent(
+          instructionIndex,
+          k -> {
+            BasicBlock block = new BasicBlock();
+            block.setNumber(basicBlockNumberGenerator.next());
+            return block;
+          });
+    }
+
+    public Value getValue(int index) {
       Value value = values[index];
       if (value == null) {
         value = new Value(valueNumberGenerator.next(), TypeElement.getBottom(), null);
@@ -130,10 +184,10 @@
       return value;
     }
 
-    public List<Value> getSsaValues(IntList indices) {
+    public List<Value> getValues(IntList indices) {
       List<Value> arguments = new ArrayList<>(indices.size());
       for (int i = 0; i < indices.size(); i++) {
-        arguments.add(getSsaValue(indices.getInt(i)));
+        arguments.add(getValue(indices.getInt(i)));
       }
       return arguments;
     }
@@ -145,7 +199,7 @@
     public Value getOutValueForNextInstruction(TypeElement type) {
       // TODO(b/225838009): Support debug locals.
       DebugLocalInfo localInfo = null;
-      int index = peekNextInstructionIndex();
+      int index = peekNextInstructionIndex() + code.getArgumentCount();
       Value value = values[index];
       if (value == null) {
         value = new Value(valueNumberGenerator.next(), type, localInfo);
@@ -159,27 +213,59 @@
       return value;
     }
 
-    private void addInstruction(Instruction instruction) {
+    public Phi getPhiForNextInstructionAndAdvanceState(TypeElement type) {
+      int index = peekNextInstructionIndex() + code.getArgumentCount();
+      // TODO(b/225838009): The phi constructor implicitly adds to the block, so we need to ensure
+      //  the block. However, we must grab the index above. Find a way to clean this up so it is
+      //  uniform with instructions.
+      advanceInstructionState();
+      // Creating the phi implicitly adds it to currentBlock.
+      DebugLocalInfo localInfo = null;
+      Phi phi =
+          new Phi(
+              valueNumberGenerator.next(), currentBlock, type, localInfo, RegisterReadType.NORMAL);
+      Value value = values[index];
+      if (value != null) {
+        // A fake ssa value has already been created, replace the users by the actual phi.
+        // TODO(b/225838009): We could consider encoding the value type as a bit in the value index
+        //  and avoid the overhead of replacing users at phi-definition time.
+        assert !value.isPhi();
+        value.replaceUsers(phi);
+      }
+      values[index] = phi;
+      return phi;
+    }
+
+    private void advanceInstructionState() {
+      ensureCurrentBlock();
       ensureCurrentPosition();
-      instruction.setPosition(currentPosition);
-      currentBlock.getInstructions().add(instruction);
-      instruction.setBlock(currentBlock);
       ++nextInstructionIndex;
     }
 
+    private void addInstruction(Instruction instruction) {
+      advanceInstructionState();
+      instruction.setPosition(currentPosition);
+      currentBlock.getInstructions().add(instruction);
+      instruction.setBlock(currentBlock);
+    }
+
     private void addThisArgument(DexType type) {
-      Argument argument = addArgument(type);
+      Argument argument = addArgument(type, 0);
       argument.outValue().markAsThis();
     }
 
-    private Argument addArgument(DexType type) {
-      Argument instruction =
-          new Argument(
-              getOutValueForNextInstruction(type.toTypeElement(appView)),
-              peekNextInstructionIndex(),
-              type.isBooleanType());
-      addInstruction(instruction);
-      return instruction;
+    private Argument addArgument(DexType type, int index) {
+      // Arguments are not included in the "instructions" so this does not call "addInstruction"
+      // which would otherwise advance the state.
+      Value dest = getValue(index);
+      dest.setType(type.toTypeElement(appView));
+      Argument argument = new Argument(dest, index, type.isBooleanType());
+      assert currentBlock != null;
+      assert currentPosition.isSyntheticPosition();
+      argument.setPosition(currentPosition);
+      currentBlock.getInstructions().add(argument);
+      argument.setBlock(currentBlock);
+      return argument;
     }
 
     @Override
@@ -189,16 +275,53 @@
     }
 
     @Override
+    public void onConstInt(int value) {
+      Value dest = getOutValueForNextInstruction(TypeElement.getInt());
+      addInstruction(new ConstNumber(dest, value));
+    }
+
+    @Override
+    public void onDivInt(int leftValueIndex, int rightValueIndex) {
+      Value dest = getOutValueForNextInstruction(TypeElement.getInt());
+      addInstruction(
+          new Div(NumericType.INT, dest, getValue(leftValueIndex), getValue(rightValueIndex)));
+    }
+
+    @Override
     public void onConstString(DexString string) {
       Value dest = getOutValueForNextInstruction(TypeElement.stringClassType(appView));
       addInstruction(new ConstString(dest, string));
     }
 
     @Override
+    public void onIf(Type ifKind, int blockIndex, int valueIndex) {
+      BasicBlock targetBlock = getBasicBlock(blockIndex);
+      Value value = getValue(valueIndex);
+      addInstruction(new If(ifKind, value));
+      currentBlock.link(targetBlock);
+      currentBlock.link(getBasicBlock(nextInstructionIndex));
+      closeCurrentBlock();
+    }
+
+    @Override
+    public void onFallthrough() {
+      int nextBlockIndex = peekNextInstructionIndex() + 1;
+      onGoto(nextBlockIndex);
+    }
+
+    @Override
+    public void onGoto(int blockIndex) {
+      BasicBlock targetBlock = getBasicBlock(blockIndex);
+      addInstruction(new Goto());
+      currentBlock.link(targetBlock);
+      closeCurrentBlock();
+    }
+
+    @Override
     public void onInvokeDirect(DexMethod target, IntList arguments) {
       // TODO(b/225838009): Maintain is-interface bit.
       Value dest = getInvokeInstructionOutputValue(target);
-      List<Value> ssaArgumentValues = getSsaValues(arguments);
+      List<Value> ssaArgumentValues = getValues(arguments);
       InvokeDirect instruction = new InvokeDirect(target, dest, ssaArgumentValues);
       addInstruction(instruction);
     }
@@ -207,7 +330,7 @@
     public void onInvokeVirtual(DexMethod target, IntList arguments) {
       // TODO(b/225838009): Maintain is-interface bit.
       Value dest = getInvokeInstructionOutputValue(target);
-      List<Value> ssaArgumentValues = getSsaValues(arguments);
+      List<Value> ssaArgumentValues = getValues(arguments);
       InvokeVirtual instruction = new InvokeVirtual(target, dest, ssaArgumentValues);
       addInstruction(instruction);
     }
@@ -227,11 +350,35 @@
     @Override
     public void onReturnVoid() {
       addInstruction(new Return());
+      closeCurrentBlock();
+    }
+
+    @Override
+    public void onArrayLength(int arrayIndex) {
+      Value dest = getOutValueForNextInstruction(TypeElement.getInt());
+      Value arrayValue = getValue(arrayIndex);
+      addInstruction(new ArrayLength(dest, arrayValue));
     }
 
     @Override
     public void onDebugPosition() {
       addInstruction(new DebugPosition());
     }
+
+    @Override
+    public void onPhi(DexType type, IntList operands) {
+      Phi phi = getPhiForNextInstructionAndAdvanceState(type.toTypeElement(appView));
+      List<Value> values = new ArrayList<>(operands.size());
+      for (int i = 0; i < operands.size(); i++) {
+        values.add(getValue(operands.getInt(i)));
+      }
+      phi.addOperands(values);
+    }
+
+    @Override
+    public void onMoveException(DexType exceptionType) {
+      Value dest = getOutValueForNextInstruction(exceptionType.toTypeElement(appView));
+      addInstruction(new MoveException(dest, exceptionType, appView.options()));
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java b/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
index fe9e5b2..2971d2e 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
@@ -4,18 +4,31 @@
 package com.android.tools.r8.lightir;
 
 import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
 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.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.IRMetadata;
+import com.android.tools.r8.ir.code.If.Type;
+import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Position.SyntheticPosition;
+import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.lightir.LIRCode.PositionEntry;
+import com.android.tools.r8.lightir.LIRCode.TryCatchTable;
 import com.android.tools.r8.utils.ListUtils;
+import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -23,17 +36,27 @@
  *
  * @param <V> Type of SSA values. This is abstract to ensure that value internals are not used in
  *     building.
+ * @param <B> Type of basic blocks. This is abstract to ensure that basic block internals are not
+ *     used in building.
  */
-public class LIRBuilder<V> {
+public class LIRBuilder<V, B> {
 
+  // Abstraction for the only accessible properties of an SSA value.
   public interface ValueIndexGetter<V> {
     int getValueIndex(V value);
   }
 
+  // Abstraction for the only accessible properties of a basic block.
+  public interface BlockIndexGetter<B> {
+    int getBlockIndex(B block);
+  }
+
+  private final DexItemFactory factory;
   private final ByteArrayWriter byteWriter = new ByteArrayWriter();
   private final LIRWriter writer = new LIRWriter(byteWriter);
   private final Reference2IntMap<DexItem> constants;
   private final ValueIndexGetter<V> valueIndexGetter;
+  private final BlockIndexGetter<B> blockIndexGetter;
   private final List<PositionEntry> positionTable;
   private int argumentCount = 0;
   private int instructionCount = 0;
@@ -42,15 +65,33 @@
   private Position currentPosition;
   private Position flushedPosition;
 
-  public LIRBuilder(DexMethod method, ValueIndexGetter<V> valueIndexGetter) {
+  private final Int2ReferenceMap<CatchHandlers<Integer>> tryCatchRanges =
+      new Int2ReferenceOpenHashMap<>();
+
+  // TODO(b/225838009): Reconsider this fixed space as the operand count for phis is much larger.
+  // Pre-allocated space for caching value indexes when writing instructions.
+  private static final int MAX_VALUE_COUNT = 10;
+  private int[] valueIndexBuffer = new int[MAX_VALUE_COUNT];
+
+  public LIRBuilder(
+      DexMethod method,
+      ValueIndexGetter<V> valueIndexGetter,
+      BlockIndexGetter<B> blockIndexGetter,
+      DexItemFactory factory) {
+    this.factory = factory;
     constants = new Reference2IntOpenHashMap<>();
     positionTable = new ArrayList<>();
     this.valueIndexGetter = valueIndexGetter;
+    this.blockIndexGetter = blockIndexGetter;
     currentPosition = SyntheticPosition.builder().setLine(0).setMethod(method).build();
     flushedPosition = currentPosition;
   }
 
-  public LIRBuilder<V> setCurrentPosition(Position position) {
+  public void addTryCatchHanders(int blockIndex, CatchHandlers<Integer> handlers) {
+    tryCatchRanges.put(blockIndex, handlers);
+  }
+
+  public LIRBuilder<V, B> setCurrentPosition(Position position) {
     assert position != null;
     assert position != Position.none();
     currentPosition = position;
@@ -75,6 +116,7 @@
 
   private void writeConstantIndex(DexItem item) {
     int index = getConstantIndex(item);
+    assert constantIndexSize(item) == ByteUtils.intEncodingSize(index);
     ByteUtils.writeEncodedInt(index, writer::writeOperand);
   }
 
@@ -90,25 +132,31 @@
     ByteUtils.writeEncodedInt(index, writer::writeOperand);
   }
 
-  public LIRBuilder<V> setMetadata(IRMetadata metadata) {
+  private int getBlockIndex(B block) {
+    return blockIndexGetter.getBlockIndex(block);
+  }
+
+  private int blockIndexSize(int index) {
+    return ByteUtils.intEncodingSize(index);
+  }
+
+  private void writeBlockIndex(int index) {
+    ByteUtils.writeEncodedInt(index, writer::writeOperand);
+  }
+
+  public LIRBuilder<V, B> setMetadata(IRMetadata metadata) {
     this.metadata = metadata;
     return this;
   }
 
-  public LIRBuilder<V> writeConstantReferencingInstruction(int opcode, DexItem item) {
-    writer.writeInstruction(opcode, constantIndexSize(item));
-    writeConstantIndex(item);
-    return this;
-  }
-
-  public LIRBuilder<V> addArgument(int index, boolean knownToBeBoolean) {
+  public LIRBuilder<V, B> addArgument(int index, boolean knownToBeBoolean) {
     // Arguments are implicitly given by method descriptor and not an actual instruction.
     assert argumentCount == index;
     argumentCount++;
     return this;
   }
 
-  private void addInstruction() {
+  private void advanceInstructionState() {
     if (!currentPosition.equals(flushedPosition)) {
       setPositionIndex(instructionCount, currentPosition);
       flushedPosition = currentPosition;
@@ -116,76 +164,236 @@
     ++instructionCount;
   }
 
-  public LIRBuilder<V> addConstNull() {
-    addInstruction();
-    writer.writeOneByteInstruction(LIROpcodes.ACONST_NULL);
+  private LIRBuilder<V, B> addNoOperandInstruction(int opcode) {
+    advanceInstructionState();
+    writer.writeOneByteInstruction(opcode);
     return this;
   }
 
-  public LIRBuilder<V> addConstInt(int value) {
-    addInstruction();
+  private LIRBuilder<V, B> addOneItemInstruction(int opcode, DexItem item) {
+    return addInstructionTemplate(opcode, Collections.singletonList(item), Collections.emptyList());
+  }
+
+  private LIRBuilder<V, B> addOneValueInstruction(int opcode, V value) {
+    return addInstructionTemplate(
+        opcode, Collections.emptyList(), Collections.singletonList(value));
+  }
+
+  private LIRBuilder<V, B> addInstructionTemplate(int opcode, List<DexItem> items, List<V> values) {
+    assert values.size() < MAX_VALUE_COUNT;
+    advanceInstructionState();
+    int operandSize = 0;
+    for (DexItem item : items) {
+      operandSize += constantIndexSize(item);
+    }
+    for (int i = 0; i < values.size(); i++) {
+      V value = values.get(i);
+      int valueIndex = getValueIndex(value);
+      operandSize += valueIndexSize(valueIndex);
+      valueIndexBuffer[i] = valueIndex;
+    }
+    writer.writeInstruction(opcode, operandSize);
+    for (DexItem item : items) {
+      writeConstantIndex(item);
+    }
+    for (int i = 0; i < values.size(); i++) {
+      writeValueIndex(valueIndexBuffer[i]);
+    }
+    return this;
+  }
+
+  public LIRBuilder<V, B> addConstNull() {
+    return addNoOperandInstruction(LIROpcodes.ACONST_NULL);
+  }
+
+  public LIRBuilder<V, B> addConstInt(int value) {
     if (0 <= value && value <= 5) {
-      writer.writeOneByteInstruction(LIROpcodes.ICONST_0 + value);
+      addNoOperandInstruction(LIROpcodes.ICONST_0 + value);
     } else {
+      advanceInstructionState();
       writer.writeInstruction(LIROpcodes.ICONST, ByteUtils.intEncodingSize(value));
       ByteUtils.writeEncodedInt(value, writer::writeOperand);
     }
     return this;
   }
 
-  public LIRBuilder<V> addConstString(DexString string) {
-    addInstruction();
-    return writeConstantReferencingInstruction(LIROpcodes.LDC, string);
-  }
-
-  public LIRBuilder<V> addStaticGet(DexField field) {
-    addInstruction();
-    return writeConstantReferencingInstruction(LIROpcodes.GETSTATIC, field);
-  }
-
-  public LIRBuilder<V> addInvokeInstruction(int opcode, DexMethod method, List<V> arguments) {
-    addInstruction();
-    int argumentOprandSize = constantIndexSize(method);
-    int[] argumentIndexes = new int[arguments.size()];
-    int i = 0;
-    for (V argument : arguments) {
-      int argumentIndex = getValueIndex(argument);
-      argumentIndexes[i++] = argumentIndex;
-      argumentOprandSize += valueIndexSize(argumentIndex);
+  public LIRBuilder<V, B> addConstNumber(ValueType type, long value) {
+    switch (type) {
+      case OBJECT:
+        return addConstNull();
+      case INT:
+        {
+          int intValue = (int) value;
+          if (-1 <= intValue && intValue <= 5) {
+            int opcode = LIROpcodes.ICONST_0 + intValue;
+            return addNoOperandInstruction(opcode);
+          }
+          int opcode = LIROpcodes.ICONST;
+          int size = ByteUtils.intEncodingSize(intValue);
+          writer.writeInstruction(opcode, size);
+          ByteUtils.writeEncodedInt(intValue, writer::writeOperand);
+          return this;
+        }
+      case FLOAT:
+      case LONG:
+      case DOUBLE:
+      default:
+        throw new Unimplemented();
     }
-    writer.writeInstruction(opcode, argumentOprandSize);
-    writeConstantIndex(method);
-    for (int argumentIndex : argumentIndexes) {
-      writeValueIndex(argumentIndex);
-    }
-    return this;
   }
 
-  public LIRBuilder<V> addInvokeDirect(DexMethod method, List<V> arguments) {
+  public LIRBuilder<V, B> addConstString(DexString string) {
+    return addOneItemInstruction(LIROpcodes.LDC, string);
+  }
+
+  public LIRBuilder<V, B> addDiv(NumericType type, V leftValue, V rightValue) {
+    switch (type) {
+      case BYTE:
+      case CHAR:
+      case SHORT:
+      case INT:
+        {
+          return addInstructionTemplate(
+              LIROpcodes.IDIV, Collections.emptyList(), ImmutableList.of(leftValue, rightValue));
+        }
+      case LONG:
+      case FLOAT:
+      case DOUBLE:
+      default:
+        throw new Unimplemented();
+    }
+  }
+
+  public LIRBuilder<V, B> addArrayLength(V array) {
+    return addOneValueInstruction(LIROpcodes.ARRAYLENGTH, array);
+  }
+
+  public LIRBuilder<V, B> addStaticGet(DexField field) {
+    return addOneItemInstruction(LIROpcodes.GETSTATIC, field);
+  }
+
+  public LIRBuilder<V, B> addInvokeInstruction(int opcode, DexMethod method, List<V> arguments) {
+    return addInstructionTemplate(opcode, Collections.singletonList(method), arguments);
+  }
+
+  public LIRBuilder<V, B> addInvokeDirect(DexMethod method, List<V> arguments) {
     return addInvokeInstruction(LIROpcodes.INVOKEDIRECT, method, arguments);
   }
 
-  public LIRBuilder<V> addInvokeVirtual(DexMethod method, List<V> arguments) {
+  public LIRBuilder<V, B> addInvokeVirtual(DexMethod method, List<V> arguments) {
     return addInvokeInstruction(LIROpcodes.INVOKEVIRTUAL, method, arguments);
   }
 
-  public LIRBuilder<V> addReturn(V value) {
+  public LIRBuilder<V, B> addReturn(V value) {
     throw new Unimplemented();
   }
 
-  public LIRBuilder<V> addReturnVoid() {
-    addInstruction();
-    writer.writeOneByteInstruction(LIROpcodes.RETURN);
+  public LIRBuilder<V, B> addReturnVoid() {
+    return addNoOperandInstruction(LIROpcodes.RETURN);
+  }
+
+  public LIRBuilder<V, B> addDebugPosition(Position position) {
+    assert currentPosition == position;
+    return addNoOperandInstruction(LIROpcodes.DEBUGPOS);
+  }
+
+  public void addFallthrough() {
+    addNoOperandInstruction(LIROpcodes.FALLTHROUGH);
+  }
+
+  public LIRBuilder<V, B> addGoto(B target) {
+    int targetIndex = getBlockIndex(target);
+    int operandSize = blockIndexSize(targetIndex);
+    advanceInstructionState();
+    writer.writeInstruction(LIROpcodes.GOTO, operandSize);
+    writeBlockIndex(targetIndex);
     return this;
   }
 
-  public LIRBuilder<V> addDebugPosition(Position position) {
-    assert currentPosition == position;
-    addInstruction();
-    writer.writeOneByteInstruction(LIROpcodes.DEBUGPOS);
+  public LIRBuilder<V, B> addIf(Type ifKind, ValueType valueType, V value, B trueTarget) {
+    int opcode;
+    switch (ifKind) {
+      case EQ:
+        opcode = valueType.isObject() ? LIROpcodes.IFNULL : LIROpcodes.IFEQ;
+        break;
+      case GE:
+        opcode = LIROpcodes.IFGE;
+        break;
+      case GT:
+        opcode = LIROpcodes.IFGT;
+        break;
+      case LE:
+        opcode = LIROpcodes.IFLE;
+        break;
+      case LT:
+        opcode = LIROpcodes.IFLT;
+        break;
+      case NE:
+        opcode = valueType.isObject() ? LIROpcodes.IFNONNULL : LIROpcodes.IFNE;
+        break;
+      default:
+        throw new Unreachable("Unexpected if kind: " + ifKind);
+    }
+    int targetIndex = getBlockIndex(trueTarget);
+    int valueIndex = getValueIndex(value);
+    int operandSize = blockIndexSize(targetIndex) + valueIndexSize(valueIndex);
+    advanceInstructionState();
+    writer.writeInstruction(opcode, operandSize);
+    writeBlockIndex(targetIndex);
+    writeValueIndex(valueIndex);
     return this;
   }
 
+  public LIRBuilder<V, B> addIfCmp(
+      Type ifKind, ValueType valueType, List<V> inValues, B trueTarget) {
+    int opcode;
+    switch (ifKind) {
+      case EQ:
+        opcode = valueType.isObject() ? LIROpcodes.IF_ACMPEQ : LIROpcodes.IF_ICMPEQ;
+        break;
+      case GE:
+        opcode = LIROpcodes.IF_ICMPGE;
+        break;
+      case GT:
+        opcode = LIROpcodes.IF_ICMPGT;
+        break;
+      case LE:
+        opcode = LIROpcodes.IF_ICMPLE;
+        break;
+      case LT:
+        opcode = LIROpcodes.IF_ICMPLT;
+        break;
+      case NE:
+        opcode = valueType.isObject() ? LIROpcodes.IF_ACMPNE : LIROpcodes.IF_ICMPNE;
+        break;
+      default:
+        throw new Unreachable("Unexpected if kind " + ifKind);
+    }
+    int targetIndex = getBlockIndex(trueTarget);
+    int valueOneIndex = getValueIndex(inValues.get(0));
+    int valueTwoIndex = getValueIndex(inValues.get(1));
+    int operandSize =
+        blockIndexSize(targetIndex) + valueIndexSize(valueOneIndex) + valueIndexSize(valueTwoIndex);
+    advanceInstructionState();
+    writer.writeInstruction(opcode, operandSize);
+    writeBlockIndex(targetIndex);
+    writeValueIndex(valueOneIndex);
+    writeValueIndex(valueTwoIndex);
+    return this;
+  }
+
+  public LIRBuilder<V, B> addMoveException(DexType exceptionType) {
+    return addOneItemInstruction(LIROpcodes.MOVEEXCEPTION, exceptionType);
+  }
+
+  public LIRBuilder<V, B> addPhi(TypeElement type, List<V> operands) {
+    DexType dexType =
+        type.isPrimitiveType()
+            ? type.asPrimitiveType().toDexType(factory)
+            : type.asReferenceType().toDexType(factory);
+    return addInstructionTemplate(LIROpcodes.PHI, Collections.singletonList(dexType), operands);
+  }
+
   public LIRCode build() {
     assert metadata != null;
     int constantsCount = constants.size();
@@ -197,6 +405,7 @@
         positionTable.toArray(new PositionEntry[positionTable.size()]),
         argumentCount,
         byteWriter.toByteArray(),
-        instructionCount);
+        instructionCount,
+        new TryCatchTable(tryCatchRanges));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRCode.java b/src/main/java/com/android/tools/r8/lightir/LIRCode.java
index 04dffd4..8caa8d6 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRCode.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRCode.java
@@ -4,12 +4,16 @@
 package com.android.tools.r8.lightir;
 
 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.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.IRMetadata;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.lightir.LIRBuilder.BlockIndexGetter;
 import com.android.tools.r8.lightir.LIRBuilder.ValueIndexGetter;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.StringUtils.BraceType;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import java.util.Arrays;
 
 public class LIRCode implements Iterable<LIRInstructionView> {
@@ -24,6 +28,18 @@
     }
   }
 
+  public static class TryCatchTable {
+    final Int2ReferenceMap<CatchHandlers<Integer>> tryCatchHandlers;
+
+    public TryCatchTable(Int2ReferenceMap<CatchHandlers<Integer>> tryCatchHandlers) {
+      this.tryCatchHandlers = tryCatchHandlers;
+    }
+
+    public CatchHandlers<Integer> getHandlersForBlock(int blockIndex) {
+      return tryCatchHandlers.get(blockIndex);
+    }
+  }
+
   private final IRMetadata metadata;
 
   /** Constant pool of items. */
@@ -34,14 +50,21 @@
   /** Full number of arguments (including receiver for non-static methods). */
   private final int argumentCount;
 
-  /** Byte encoding of the instructions (including phis). */
+  /** Byte encoding of the instructions (excludes arguments, includes phis). */
   private final byte[] instructions;
 
-  /** Cached value for the number of logical instructions (including phis). */
+  /** Cached value for the number of logical instructions (excludes arguments, includes phis). */
   private final int instructionCount;
 
-  public static <V> LIRBuilder<V> builder(DexMethod method, ValueIndexGetter<V> valueIndexGetter) {
-    return new LIRBuilder<V>(method, valueIndexGetter);
+  /** Table of try-catch handlers for each basic block. */
+  private final TryCatchTable tryCatchTable;
+
+  public static <V, B> LIRBuilder<V, B> builder(
+      DexMethod method,
+      ValueIndexGetter<V> valueIndexGetter,
+      BlockIndexGetter<B> blockIndexGetter,
+      DexItemFactory factory) {
+    return new LIRBuilder<V, B>(method, valueIndexGetter, blockIndexGetter, factory);
   }
 
   // Should be constructed using LIRBuilder.
@@ -51,13 +74,15 @@
       PositionEntry[] positions,
       int argumentCount,
       byte[] instructions,
-      int instructionCount) {
+      int instructionCount,
+      TryCatchTable tryCatchTable) {
     this.metadata = metadata;
     this.constants = constants;
     this.positionTable = positions;
     this.argumentCount = argumentCount;
     this.instructions = instructions;
     this.instructionCount = instructionCount;
+    this.tryCatchTable = tryCatchTable;
   }
 
   public int getArgumentCount() {
@@ -84,6 +109,10 @@
     return positionTable;
   }
 
+  public TryCatchTable getTryCatchTable() {
+    return tryCatchTable;
+  }
+
   @Override
   public LIRIterator iterator() {
     return new LIRIterator(new ByteArrayIterator(instructions));
@@ -102,6 +131,7 @@
         .append("):{");
     int index = 0;
     for (LIRInstructionView view : this) {
+      builder.append(index).append(':');
       builder.append(LIROpcodes.toString(view.getOpcode()));
       if (view.getRemainingOperandSizeInBytes() > 0) {
         builder.append("(size:").append(1 + view.getRemainingOperandSizeInBytes()).append(")");
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRInstructionView.java b/src/main/java/com/android/tools/r8/lightir/LIRInstructionView.java
index f42abdb..428c01f 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRInstructionView.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRInstructionView.java
@@ -15,6 +15,9 @@
   /** Convenience method to forward control to a callback. */
   void accept(LIRInstructionCallback eventCallback);
 
+  /** Get the instruction index. */
+  int getInstructionIndex();
+
   /** The opcode of the instruction (See {@code LIROpcodes} for values). */
   int getOpcode();
 
@@ -24,9 +27,15 @@
   /** True if the instruction has any operands that have not yet been parsed. */
   boolean hasMoreOperands();
 
+  /** Get the next operand as an encoded integer */
+  int getNextIntegerOperand();
+
   /** Get the next operand as a constant-pool index. */
   int getNextConstantOperand();
 
   /** Get the next operand as an SSA value index. */
   int getNextValueOperand();
+
+  /** Get the next operand as a basic-block index. */
+  int getNextBlockOperand();
 }
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRIterator.java b/src/main/java/com/android/tools/r8/lightir/LIRIterator.java
index 9659aa5..882e960 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRIterator.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRIterator.java
@@ -16,10 +16,14 @@
 
   private final ByteIterator iterator;
 
+  // State of the byte offsets into the iterator.
   private int currentByteIndex = 0;
-  private int currentOpcode = -1;
   private int endOfCurrentInstruction = 0;
 
+  // State of the instruction interpretation.
+  private int currentInstructionIndex = -1;
+  private int currentOpcode = -1;
+
   public LIRIterator(ByteIterator iterator) {
     this.iterator = iterator;
   }
@@ -39,6 +43,7 @@
   @Override
   public LIRInstructionView next() {
     skipRemainingOperands();
+    ++currentInstructionIndex;
     currentOpcode = u1();
     if (LIROpcodes.isOneByteInstruction(currentOpcode)) {
       endOfCurrentInstruction = currentByteIndex;
@@ -57,6 +62,11 @@
   }
 
   @Override
+  public int getInstructionIndex() {
+    return currentInstructionIndex;
+  }
+
+  @Override
   public int getOpcode() {
     return currentOpcode;
   }
@@ -72,15 +82,24 @@
   }
 
   @Override
-  public int getNextConstantOperand() {
+  public int getNextIntegerOperand() {
     assert hasMoreOperands();
     return u4();
   }
 
   @Override
+  public int getNextConstantOperand() {
+    return getNextIntegerOperand();
+  }
+
+  @Override
   public int getNextValueOperand() {
-    assert hasMoreOperands();
-    return u4();
+    return getNextIntegerOperand();
+  }
+
+  @Override
+  public int getNextBlockOperand() {
+    return getNextIntegerOperand();
   }
 
   private void skip(int i) {
diff --git a/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java b/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java
index 089469f..0b0dfc8 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java
@@ -14,7 +14,7 @@
 
   static boolean isOneByteInstruction(int opcode) {
     assert opcode >= ACONST_NULL;
-    return opcode <= DCONST_1 || opcode == RETURN || opcode == DEBUGPOS;
+    return opcode <= DCONST_1 || opcode == RETURN || opcode == DEBUGPOS || opcode == FALLTHROUGH;
   }
 
   // Instructions maintaining the same opcode as defined in CF.
@@ -183,6 +183,9 @@
   int DCONST = 203;
   int INVOKEDIRECT = 204;
   int DEBUGPOS = 205;
+  int PHI = 206;
+  int FALLTHROUGH = 207;
+  int MOVEEXCEPTION = 208;
 
   static String toString(int opcode) {
     switch (opcode) {
@@ -489,6 +492,12 @@
         return "INVOKEDIRECT";
       case DEBUGPOS:
         return "DEBUGPOS";
+      case PHI:
+        return "PHI";
+      case FALLTHROUGH:
+        return "FALLTHROUGH";
+      case MOVEEXCEPTION:
+        return "MOVEEXCEPTION";
 
       default:
         throw new Unreachable("Unexpected LIR opcode: " + opcode);
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRParsedInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LIRParsedInstructionCallback.java
index 317eeee..5c87faf 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRParsedInstructionCallback.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRParsedInstructionCallback.java
@@ -8,6 +8,9 @@
 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.ir.code.If;
+import com.android.tools.r8.ir.code.NumericType;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
 
@@ -32,8 +35,28 @@
 
   public void onConstNull() {}
 
+  public void onConstNumber(NumericType type, long value) {}
+
+  public void onConstInt(int value) {
+    onConstNumber(NumericType.INT, value);
+  }
+
   public void onConstString(DexString string) {}
 
+  public void onDiv(NumericType type, int leftValueIndex, int rightValueIndex) {}
+
+  public void onDivInt(int leftValueIndex, int rightValueIndex) {
+    onDiv(NumericType.INT, leftValueIndex, rightValueIndex);
+  }
+
+  public void onIf(If.Type ifKind, int blockIndex, int valueIndex) {}
+
+  public void onGoto(int blockIndex) {}
+
+  public void onFallthrough() {}
+
+  public void onMoveException(DexType exceptionType) {}
+
   public void onInvokeMethodInstruction(DexMethod method, IntList arguments) {}
 
   public void onInvokeDirect(DexMethod method, IntList arguments) {
@@ -54,60 +77,130 @@
 
   public void onReturnVoid() {}
 
+  public void onArrayLength(int arrayIndex) {}
+
   public void onDebugPosition() {}
 
+  public void onPhi(DexType type, IntList operands) {}
+
   private DexItem getConstantItem(int index) {
     return code.getConstantItem(index);
   }
 
   @Override
   public final void onInstructionView(LIRInstructionView view) {
-    switch (view.getOpcode()) {
+    int opcode = view.getOpcode();
+    switch (opcode) {
       case LIROpcodes.ACONST_NULL:
         {
           onConstNull();
-          break;
+          return;
         }
       case LIROpcodes.LDC:
         {
           DexItem item = getConstantItem(view.getNextConstantOperand());
           if (item instanceof DexString) {
             onConstString((DexString) item);
+            return;
           }
-          break;
+          throw new Unimplemented();
+        }
+      case LIROpcodes.ICONST_M1:
+      case LIROpcodes.ICONST_0:
+      case LIROpcodes.ICONST_1:
+      case LIROpcodes.ICONST_2:
+      case LIROpcodes.ICONST_3:
+      case LIROpcodes.ICONST_4:
+      case LIROpcodes.ICONST_5:
+        {
+          int value = opcode - LIROpcodes.ICONST_0;
+          onConstInt(value);
+          return;
+        }
+      case LIROpcodes.ICONST:
+        {
+          int value = view.getNextIntegerOperand();
+          onConstInt(value);
+          return;
+        }
+      case LIROpcodes.IDIV:
+        {
+          int leftValueIndex = view.getNextValueOperand();
+          int rightValueIndex = view.getNextValueOperand();
+          onDivInt(leftValueIndex, rightValueIndex);
+          return;
+        }
+      case LIROpcodes.IFNE:
+        {
+          int blockIndex = view.getNextBlockOperand();
+          int valueIndex = view.getNextValueOperand();
+          onIf(If.Type.NE, blockIndex, valueIndex);
+          return;
+        }
+      case LIROpcodes.GOTO:
+        {
+          int blockIndex = view.getNextBlockOperand();
+          onGoto(blockIndex);
+          return;
         }
       case LIROpcodes.INVOKEDIRECT:
         {
           DexMethod target = getInvokeInstructionTarget(view);
           IntList arguments = getInvokeInstructionArguments(view);
           onInvokeDirect(target, arguments);
-          break;
+          return;
         }
       case LIROpcodes.INVOKEVIRTUAL:
         {
           DexMethod target = getInvokeInstructionTarget(view);
           IntList arguments = getInvokeInstructionArguments(view);
           onInvokeVirtual(target, arguments);
-          break;
+          return;
         }
       case LIROpcodes.GETSTATIC:
         {
           DexField field = (DexField) getConstantItem(view.getNextConstantOperand());
           onStaticGet(field);
-          break;
+          return;
         }
       case LIROpcodes.RETURN:
         {
           onReturnVoid();
-          break;
+          return;
+        }
+      case LIROpcodes.ARRAYLENGTH:
+        {
+          onArrayLength(view.getNextValueOperand());
+          return;
         }
       case LIROpcodes.DEBUGPOS:
         {
           onDebugPosition();
-          break;
+          return;
+        }
+      case LIROpcodes.PHI:
+        {
+          DexType type = (DexType) getConstantItem(view.getNextConstantOperand());
+          IntList operands = new IntArrayList();
+          while (view.hasMoreOperands()) {
+            operands.add(view.getNextValueOperand());
+          }
+          onPhi(type, operands);
+          return;
+        }
+      case LIROpcodes.FALLTHROUGH:
+        {
+          onFallthrough();
+          return;
+        }
+      case LIROpcodes.MOVEEXCEPTION:
+        {
+          DexType type = (DexType) getConstantItem(view.getNextConstantOperand());
+          onMoveException(type);
+          return;
         }
       default:
-        throw new Unimplemented("No dispatch for opcode " + LIROpcodes.toString(view.getOpcode()));
+        throw new Unimplemented("No dispatch for opcode " + LIROpcodes.toString(opcode));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java b/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java
index 77ea56c..11e1377 100644
--- a/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java
+++ b/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java
@@ -55,7 +55,7 @@
 
   @Override
   public void registerInvokeDirect(DexMethod method) {
-    invalid();
+    setTarget(method, InvokeKind.DIRECT);
   }
 
   @Override
@@ -109,6 +109,7 @@
   }
 
   public enum InvokeKind {
+    DIRECT,
     VIRTUAL,
     STATIC,
     SUPER,
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
index 6782981..e5b23f5 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
@@ -109,6 +109,9 @@
 
   @Override
   public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) {
+    if (this == applied) {
+      return originalMethod;
+    }
     return getPrevious().getRenamedMethodSignature(originalMethod, applied);
   }
 
@@ -152,25 +155,32 @@
   }
 
   public MemberRebindingIdentityLens toRewrittenMemberRebindingIdentityLens(
-      AppView<? extends AppInfoWithClassHierarchy> appView, GraphLens lens) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      GraphLens lens,
+      NonIdentityGraphLens appliedMemberRebindingLens) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     Builder builder = builder(appView, getIdentityLens());
     nonReboundFieldReferenceToDefinitionMap.forEach(
         (nonReboundFieldReference, reboundFieldReference) -> {
-          DexField rewrittenReboundFieldReference = lens.lookupField(reboundFieldReference);
+          DexField rewrittenReboundFieldReference =
+              lens.lookupField(reboundFieldReference, appliedMemberRebindingLens);
           DexField rewrittenNonReboundFieldReference =
               rewrittenReboundFieldReference.withHolder(
-                  lens.lookupType(nonReboundFieldReference.getHolderType()), dexItemFactory);
+                  lens.lookupType(
+                      nonReboundFieldReference.getHolderType(), appliedMemberRebindingLens),
+                  dexItemFactory);
           builder.recordNonReboundFieldAccess(
               rewrittenNonReboundFieldReference, rewrittenReboundFieldReference);
         });
     nonReboundMethodReferenceToDefinitionMap.forEach(
         (nonReboundMethodReference, reboundMethodReference) -> {
           DexMethod rewrittenReboundMethodReference =
-              lens.getRenamedMethodSignature(reboundMethodReference);
+              lens.getRenamedMethodSignature(reboundMethodReference, appliedMemberRebindingLens);
           DexMethod rewrittenNonReboundMethodReference =
               rewrittenReboundMethodReference.withHolder(
-                  lens.lookupType(nonReboundMethodReference.getHolderType()), dexItemFactory);
+                  lens.lookupType(
+                      nonReboundMethodReference.getHolderType(), appliedMemberRebindingLens),
+                  dexItemFactory);
           builder.recordNonReboundMethodAccess(
               rewrittenNonReboundMethodReference, rewrittenReboundMethodReference);
         });
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
index 5065a75..306d53e 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
@@ -144,16 +144,20 @@
   }
 
   public FieldRebindingIdentityLens toRewrittenFieldRebindingLens(
-      AppView<? extends AppInfoWithClassHierarchy> appView, GraphLens lens) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      GraphLens lens,
+      NonIdentityGraphLens appliedMemberRebindingLens) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     FieldRebindingIdentityLens.Builder builder = FieldRebindingIdentityLens.builder();
     nonReboundFieldReferenceToDefinitionMap.forEach(
         (nonReboundFieldReference, reboundFieldReference) -> {
           DexField rewrittenReboundFieldReference =
-              lens.getRenamedFieldSignature(reboundFieldReference);
+              lens.getRenamedFieldSignature(reboundFieldReference, appliedMemberRebindingLens);
           DexField rewrittenNonReboundFieldReference =
               rewrittenReboundFieldReference.withHolder(
-                  lens.lookupType(nonReboundFieldReference.getHolderType()), dexItemFactory);
+                  lens.lookupType(
+                      nonReboundFieldReference.getHolderType(), appliedMemberRebindingLens),
+                  dexItemFactory);
           builder.recordDefinitionForNonReboundFieldReference(
               rewrittenNonReboundFieldReference, rewrittenReboundFieldReference);
         });
diff --git a/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java
new file mode 100644
index 0000000..6c2af55
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java
@@ -0,0 +1,147 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.optimize;
+
+import static com.android.tools.r8.utils.ThreadUtils.processItems;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
+import com.android.tools.r8.optimize.InvokeSingleTargetExtractor.InvokeKind;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class RedundantBridgeRemover {
+
+  private final AppView<AppInfoWithLiveness> appView;
+
+  public RedundantBridgeRemover(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+  }
+
+  private boolean isRedundantBridge(ProgramMethod method) {
+    // Clean-up the predicate check.
+    if (appView.appInfo().isPinned(method.getReference())) {
+      return false;
+    }
+    DexEncodedMethod definition = method.getDefinition();
+    // TODO(b/197490164): Remove if method is abstract.
+    BridgeInfo bridgeInfo = definition.getOptimizationInfo().getBridgeInfo();
+    boolean isBridge = definition.isBridge() || bridgeInfo != null;
+    if (!isBridge || definition.isAbstract()) {
+      return false;
+    }
+    InvokeSingleTargetExtractor targetExtractor = new InvokeSingleTargetExtractor(appView, method);
+    method.registerCodeReferences(targetExtractor);
+    DexMethod target = targetExtractor.getTarget();
+    // javac-generated visibility forward bridge method has same descriptor (name, signature and
+    // return type).
+    if (target == null || !target.match(method.getReference())) {
+      return false;
+    }
+    if (!isTargetingSuperMethod(method, targetExtractor.getKind(), target)) {
+      return false;
+    }
+    // This is a visibility forward, so check for the direct target.
+    ProgramMethod targetMethod =
+        appView
+            .appInfo()
+            .unsafeResolveMethodDueToDexFormatLegacy(target)
+            .getResolvedProgramMethod();
+    if (targetMethod == null) {
+      return false;
+    }
+    if (method.getAccessFlags().isPublic()) {
+      if (!targetMethod.getAccessFlags().isPublic()) {
+        return false;
+      }
+    } else {
+      if (targetMethod.getAccessFlags().isProtected()
+          && !targetMethod.getHolderType().isSamePackage(method.getHolderType())) {
+        return false;
+      }
+      if (targetMethod.getAccessFlags().isPrivate()) {
+        return false;
+      }
+    }
+    if (definition.isStatic()
+        && method.getHolder().hasClassInitializer()
+        && method
+            .getHolder()
+            .classInitializationMayHaveSideEffectsInContext(appView, targetMethod)) {
+      return false;
+    }
+    return true;
+  }
+
+  private boolean isTargetingSuperMethod(ProgramMethod method, InvokeKind kind, DexMethod target) {
+    if (kind == InvokeKind.ILLEGAL) {
+      return false;
+    }
+    if (kind == InvokeKind.DIRECT) {
+      return method.getDefinition().isInstanceInitializer()
+          && appView.options().canHaveNonReboundConstructorInvoke()
+          && appView.testing().enableRedundantConstructorBridgeRemoval
+          && appView.appInfo().isStrictSubtypeOf(method.getHolderType(), target.getHolderType());
+    }
+    assert !method.getAccessFlags().isPrivate();
+    assert !method.getDefinition().isInstanceInitializer();
+    if (kind == InvokeKind.SUPER) {
+      return true;
+    }
+    if (kind == InvokeKind.STATIC) {
+      return appView.appInfo().isStrictSubtypeOf(method.getHolderType(), target.holder);
+    }
+    assert false : "Unexpected invoke-kind for visibility bridge: " + kind;
+    return false;
+  }
+
+  public void run(ExecutorService executorService) throws ExecutionException {
+    // Collect all redundant bridges to remove.
+    Map<DexProgramClass, ProgramMethodSet> bridgesToRemove =
+        computeBridgesToRemove(executorService);
+    pruneApp(bridgesToRemove, executorService);
+  }
+
+  private Map<DexProgramClass, ProgramMethodSet> computeBridgesToRemove(
+      ExecutorService executorService) throws ExecutionException {
+    Map<DexProgramClass, ProgramMethodSet> bridgesToRemove = new ConcurrentHashMap<>();
+    processItems(
+        appView.appInfo().classes(),
+        clazz -> {
+          ProgramMethodSet bridgesToRemoveForClass = ProgramMethodSet.create();
+          clazz.forEachProgramMethod(
+              method -> {
+                if (isRedundantBridge(method)) {
+                  bridgesToRemoveForClass.add(method);
+                }
+              });
+          if (!bridgesToRemoveForClass.isEmpty()) {
+            bridgesToRemove.put(clazz, bridgesToRemoveForClass);
+          }
+        },
+        executorService);
+    return bridgesToRemove;
+  }
+
+  private void pruneApp(
+      Map<DexProgramClass, ProgramMethodSet> bridgesToRemove, ExecutorService executorService)
+      throws ExecutionException {
+    PrunedItems.Builder prunedItemsBuilder = PrunedItems.builder().setPrunedApp(appView.app());
+    bridgesToRemove.forEach(
+        (clazz, methods) -> {
+          clazz.getMethodCollection().removeMethods(methods.toDefinitionSet());
+          methods.forEach(method -> prunedItemsBuilder.addRemovedMethod(method.getReference()));
+        });
+    appView.pruneItems(prunedItemsBuilder.build(), executorService);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
deleted file mode 100644
index 3dd736b..0000000
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.optimize;
-
-import static com.android.tools.r8.utils.ThreadUtils.processItems;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.PrunedItems;
-import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.optimize.InvokeSingleTargetExtractor.InvokeKind;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.google.common.collect.Sets;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-
-public class VisibilityBridgeRemover {
-
-  private final AppView<AppInfoWithLiveness> appView;
-
-  public VisibilityBridgeRemover(AppView<AppInfoWithLiveness> appView) {
-    this.appView = appView;
-  }
-
-  private boolean isUnneededVisibilityBridge(ProgramMethod method) {
-    // Clean-up the predicate check.
-    if (appView.appInfo().isPinned(method.getReference())) {
-      return false;
-    }
-    DexEncodedMethod definition = method.getDefinition();
-    // TODO(b/198133259): Extend to definitions that are not defined as bridges.
-    // TODO(b/197490164): Remove if method is abstract.
-    if (!definition.isBridge() || definition.isAbstract()) {
-      return false;
-    }
-    InvokeSingleTargetExtractor targetExtractor = new InvokeSingleTargetExtractor(appView, method);
-    method.registerCodeReferences(targetExtractor);
-    DexMethod target = targetExtractor.getTarget();
-    // javac-generated visibility forward bridge method has same descriptor (name, signature and
-    // return type).
-    if (target == null || !target.match(method.getReference())) {
-      return false;
-    }
-    assert !definition.isPrivate() && !definition.isInstanceInitializer();
-    if (!isTargetingSuperMethod(method, targetExtractor.getKind(), target)) {
-      return false;
-    }
-    // This is a visibility forward, so check for the direct target.
-    ProgramMethod targetMethod =
-        appView
-            .appInfo()
-            .unsafeResolveMethodDueToDexFormatLegacy(target)
-            .getResolvedProgramMethod();
-    if (targetMethod == null || !targetMethod.getAccessFlags().isPublic()) {
-      return false;
-    }
-    if (definition.isStatic()
-        && method.getHolder().hasClassInitializer()
-        && method
-            .getHolder()
-            .classInitializationMayHaveSideEffectsInContext(appView, targetMethod)) {
-      return false;
-    }
-    if (Log.ENABLED) {
-      Log.info(
-          getClass(),
-          "Removing visibility forwarding %s -> %s",
-          method,
-          targetMethod.getReference());
-    }
-    return true;
-  }
-
-  private boolean isTargetingSuperMethod(ProgramMethod method, InvokeKind kind, DexMethod target) {
-    if (kind == InvokeKind.ILLEGAL) {
-      return false;
-    }
-    if (kind == InvokeKind.SUPER) {
-      return true;
-    }
-    if (kind == InvokeKind.STATIC) {
-      return appView.appInfo().isStrictSubtypeOf(method.getHolderType(), target.holder);
-    }
-    assert false : "Unexpected invoke-kind for visibility bridge: " + kind;
-    return false;
-  }
-
-  public void run(ExecutorService executorService) throws ExecutionException {
-    // Collect all visibility bridges to remove.
-    if (!appView.options().enableVisibilityBridgeRemoval) {
-      return;
-    }
-    ConcurrentHashMap<DexProgramClass, Set<DexEncodedMethod>> visibilityBridgesToRemove =
-        new ConcurrentHashMap<>();
-    processItems(
-        appView.appInfo().classes(),
-        clazz -> {
-          Set<DexEncodedMethod> bridgesToRemoveForClass = Sets.newIdentityHashSet();
-          clazz.forEachProgramMethod(
-              method -> {
-                if (isUnneededVisibilityBridge(method)) {
-                  bridgesToRemoveForClass.add(method.getDefinition());
-                }
-              });
-          if (!bridgesToRemoveForClass.isEmpty()) {
-            visibilityBridgesToRemove.put(clazz, bridgesToRemoveForClass);
-          }
-        },
-        executorService);
-    // Remove all bridges found.
-    PrunedItems.Builder builder = PrunedItems.builder();
-    visibilityBridgesToRemove.forEach(
-        (clazz, methods) -> {
-          clazz.getMethodCollection().removeMethods(methods);
-          methods.forEach(method -> builder.addRemovedMethod(method.getReference()));
-        });
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
index 09966ca..e6df4a0 100644
--- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
@@ -125,8 +125,8 @@
     Set<DexProgramClass> subclasses = new TreeSet<>(Comparator.comparing(DexClass::getType));
     for (DexType subtype : subtypes) {
       DexProgramClass subclass = asProgramClassOrNull(appView.definitionFor(subtype));
-      if (subclass == null) {
-        return;
+      if (subclass == null || !appView.testing().isEligibleForBridgeHoisting.test(subclass)) {
+        continue;
       }
       subclasses.add(subclass);
     }
@@ -141,7 +141,7 @@
     for (DexProgramClass subclass : subclasses) {
       for (DexEncodedMethod method : subclass.virtualMethods()) {
         BridgeInfo bridgeInfo = method.getOptimizationInfo().getBridgeInfo();
-        if (bridgeInfo != null) {
+        if (bridgeInfo != null && bridgeInfo.isVirtualBridgeInfo()) {
           candidates.add(equivalence.wrap(method.getReference()));
         }
       }
@@ -158,6 +158,18 @@
       return;
     }
 
+    // Bail out if the bridge is also declared in the parent class. In that case, hoisting would
+    // change the behavior of calling the bridge on an instance of the parent class.
+    MethodResolutionResult res =
+        appView.appInfo().resolveMethodOnClass(clazz.getSuperType(), method);
+    if (res.isSingleResolution()) {
+      if (!res.getResolvedMethod().isAbstract()) {
+        return;
+      }
+    } else if (res.isMultiMethodResolutionResult()) {
+      return;
+    }
+
     // Go through each of the subclasses and find the bridges that can be hoisted. The bridge holder
     // classes are stored in buckets grouped by the behavior of the body of the bridge (which is
     // implicitly defined by the signature of the invoke-virtual instruction).
@@ -187,7 +199,7 @@
       }
 
       BridgeInfo currentBridgeInfo = definition.getOptimizationInfo().getBridgeInfo();
-      if (currentBridgeInfo == null) {
+      if (currentBridgeInfo == null || currentBridgeInfo.isDirectBridgeInfo()) {
         // This is not a bridge, so the method needs to remain on the subclass.
         continue;
       }
@@ -196,14 +208,27 @@
 
       VirtualBridgeInfo currentVirtualBridgeInfo = currentBridgeInfo.asVirtualBridgeInfo();
       DexMethod invokedMethod = currentVirtualBridgeInfo.getInvokedMethod();
+
+      if (!clazz.getType().isSamePackage(subclass.getType())) {
+        DexEncodedMethod resolvedMethod =
+            appView.appInfo().resolveMethodOnClass(clazz, invokedMethod).getSingleTarget();
+        if (resolvedMethod == null || resolvedMethod.getAccessFlags().isPackagePrivate()) {
+          // After hoisting this bridge would now dispatch to another method, namely the package
+          // private method in the parent class.
+          continue;
+        }
+      }
+
       Wrapper<DexMethod> wrapper = MethodSignatureEquivalence.get().wrap(invokedMethod);
       eligibleVirtualInvokeBridges
           .computeIfAbsent(wrapper, ignore -> new ArrayList<>())
           .add(subclass);
     }
 
-    // There should be at least one method that is eligible for hoisting.
-    assert !eligibleVirtualInvokeBridges.isEmpty();
+    // Check if any bridges may be eligible for hoisting.
+    if (eligibleVirtualInvokeBridges.isEmpty()) {
+      return;
+    }
 
     Entry<Wrapper<DexMethod>, List<DexProgramClass>> mostFrequentBridge =
         findMostFrequentBridge(eligibleVirtualInvokeBridges);
@@ -217,7 +242,7 @@
     ProgramMethod representative = eligibleBridgeMethods.iterator().next();
 
     // Guard against accessibility issues.
-    if (mayBecomeInaccessibleAfterHoisting(clazz, representative)) {
+    if (mayBecomeInaccessibleAfterHoisting(clazz, eligibleBridgeMethods, representative)) {
       return;
     }
 
@@ -287,11 +312,21 @@
   }
 
   private boolean mayBecomeInaccessibleAfterHoisting(
-      DexProgramClass clazz, ProgramMethod representative) {
-    if (clazz.type.isSamePackage(representative.getHolder().type)) {
-      return false;
+      DexProgramClass clazz,
+      List<ProgramMethod> eligibleBridgeMethods,
+      ProgramMethod representative) {
+    int representativeVisibility = representative.getAccessFlags().getVisibilityOrdinal();
+    for (ProgramMethod eligibleBridgeMethod : eligibleBridgeMethods) {
+      if (eligibleBridgeMethod.getAccessFlags().getVisibilityOrdinal()
+          != representativeVisibility) {
+        return true;
+      }
+      if (!clazz.getType().isSamePackage(eligibleBridgeMethod.getHolderType())
+          && !eligibleBridgeMethod.getDefinition().isPublic()) {
+        return true;
+      }
     }
-    return !representative.getDefinition().isPublic();
+    return false;
   }
 
   private Code createCodeForVirtualBridge(ProgramMethod representative, DexMethod methodToInvoke) {
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
new file mode 100644
index 0000000..11d080b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
@@ -0,0 +1,213 @@
+// Copyright (c) 2022, 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.profile.art;
+
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.TextInputStream;
+import com.android.tools.r8.TextOutputStream;
+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.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UncheckedIOException;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class ArtProfile {
+
+  private final List<ArtProfileRule> rules;
+
+  ArtProfile(List<ArtProfileRule> rules) {
+    assert !rules.isEmpty();
+    this.rules = rules;
+  }
+
+  public static Builder builder(ArtProfileProvider artProfileProvider, InternalOptions options) {
+    return new Builder(artProfileProvider, options);
+  }
+
+  public ArtProfile rewrittenWithLens(GraphLens lens) {
+    return transform(
+        (classRule, builderFactory) -> builderFactory.accept(lens.lookupType(classRule.getType())),
+        (methodRule, builderFactory) ->
+            builderFactory
+                .apply(lens.getRenamedMethodSignature(methodRule.getMethod()))
+                .acceptMethodRuleInfoBuilder(
+                    methodRuleInfoBuilder ->
+                        methodRuleInfoBuilder.merge(methodRule.getMethodRuleInfo())));
+  }
+
+  public ArtProfile rewrittenWithLens(NamingLens lens, DexItemFactory dexItemFactory) {
+    assert !lens.isIdentityLens();
+    return transform(
+        (classRule, builderFactory) ->
+            builderFactory.accept(lens.lookupType(classRule.getType(), dexItemFactory)),
+        (methodRule, builderFactory) ->
+            builderFactory
+                .apply(lens.lookupMethod(methodRule.getMethod(), dexItemFactory))
+                .acceptMethodRuleInfoBuilder(
+                    methodRuleInfoBuilder ->
+                        methodRuleInfoBuilder.merge(methodRule.getMethodRuleInfo())));
+  }
+
+  public ArtProfile withoutPrunedItems(PrunedItems prunedItems) {
+    return transform(
+        (classRule, builderFactory) -> {
+          if (!prunedItems.isRemoved(classRule.getType())) {
+            builderFactory.accept(classRule.getType());
+          }
+        },
+        (methodRule, builderFactory) -> {
+          if (!prunedItems.isRemoved(methodRule.getMethod())) {
+            builderFactory
+                .apply(methodRule.getMethod())
+                .acceptMethodRuleInfoBuilder(
+                    methodRuleInfoBuilder ->
+                        methodRuleInfoBuilder.merge(methodRule.getMethodRuleInfo()));
+          }
+        });
+  }
+
+  private ArtProfile transform(
+      BiConsumer<ArtProfileClassRule, Consumer<DexType>> classTransformation,
+      BiConsumer<ArtProfileMethodRule, Function<DexMethod, ArtProfileMethodRule.Builder>>
+          methodTransformation) {
+    Map<DexReference, ArtProfileRule.Builder> ruleBuilders = new LinkedHashMap<>();
+    for (ArtProfileRule rule : rules) {
+      if (rule.isClassRule()) {
+        // Supply a factory method for creating a builder. If the current rule should be included in
+        // the rewritten profile, the caller should call the provided builder factory method to
+        // create a class rule builder. If two rules are mapped to the same reference, the same rule
+        // builder is reused so that the two rules are merged into a single rule (with their flags
+        // merged).
+        classTransformation.accept(
+            rule.asClassRule(),
+            newType ->
+                ruleBuilders
+                    .computeIfAbsent(
+                        newType, ignoreKey(() -> ArtProfileClassRule.builder().setType(newType)))
+                    .asClassRuleBuilder());
+      } else {
+        // As above.
+        assert rule.isMethodRule();
+        methodTransformation.accept(
+            rule.asMethodRule(),
+            newMethod ->
+                ruleBuilders
+                    .computeIfAbsent(
+                        newMethod,
+                        ignoreKey(() -> ArtProfileMethodRule.builder().setMethod(newMethod)))
+                    .asMethodRuleBuilder());
+      }
+    }
+    ImmutableList.Builder<ArtProfileRule> newRules =
+        ImmutableList.builderWithExpectedSize(ruleBuilders.size());
+    ruleBuilders.values().forEach(ruleBuilder -> newRules.add(ruleBuilder.build()));
+    return new ArtProfile(newRules.build());
+  }
+
+  public void supplyConsumer(ArtProfileConsumer consumer, Reporter reporter) {
+    if (consumer != null) {
+      TextOutputStream textOutputStream = consumer.getHumanReadableArtProfileConsumer();
+      if (textOutputStream != null) {
+        supplyHumanReadableArtProfileConsumer(textOutputStream);
+      }
+      ArtProfileRuleConsumer ruleConsumer = consumer.getRuleConsumer();
+      if (ruleConsumer != null) {
+        supplyRuleConsumer(ruleConsumer);
+      }
+      consumer.finished(reporter);
+    }
+  }
+
+  private void supplyHumanReadableArtProfileConsumer(TextOutputStream textOutputStream) {
+    try {
+      try (OutputStreamWriter outputStreamWriter =
+          new OutputStreamWriter(
+              textOutputStream.getOutputStream(), textOutputStream.getCharset())) {
+        for (ArtProfileRule rule : rules) {
+          rule.writeHumanReadableRuleString(outputStreamWriter);
+          outputStreamWriter.write('\n');
+        }
+      }
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    }
+  }
+
+  private void supplyRuleConsumer(ArtProfileRuleConsumer ruleConsumer) {
+    for (ArtProfileRule rule : rules) {
+      rule.accept(
+          classRule ->
+              ruleConsumer.acceptClassRule(
+                  classRule.getClassReference(), classRule.getClassRuleInfo()),
+          methodRule ->
+              ruleConsumer.acceptMethodRule(
+                  methodRule.getMethodReference(), methodRule.getMethodRuleInfo()));
+    }
+  }
+
+  public static class Builder implements ArtProfileBuilder {
+
+    private final ArtProfileProvider artProfileProvider;
+    private final DexItemFactory dexItemFactory;
+    private Reporter reporter;
+    private final List<ArtProfileRule> rules = new ArrayList<>();
+
+    Builder(ArtProfileProvider artProfileProvider, InternalOptions options) {
+      this.artProfileProvider = artProfileProvider;
+      this.dexItemFactory = options.dexItemFactory();
+      this.reporter = options.reporter;
+    }
+
+    @Override
+    public ArtProfileBuilder addClassRule(
+        Consumer<ArtProfileClassRuleBuilder> classRuleBuilderConsumer) {
+      ArtProfileClassRule.Builder classRuleBuilder = ArtProfileClassRule.builder(dexItemFactory);
+      classRuleBuilderConsumer.accept(classRuleBuilder);
+      rules.add(classRuleBuilder.build());
+      return this;
+    }
+
+    @Override
+    public ArtProfileBuilder addMethodRule(
+        Consumer<ArtProfileMethodRuleBuilder> methodRuleBuilderConsumer) {
+      ArtProfileMethodRule.Builder methodRuleBuilder = ArtProfileMethodRule.builder(dexItemFactory);
+      methodRuleBuilderConsumer.accept(methodRuleBuilder);
+      rules.add(methodRuleBuilder.build());
+      return this;
+    }
+
+    @Override
+    public ArtProfileBuilder addHumanReadableArtProfile(
+        TextInputStream textInputStream,
+        Consumer<HumanReadableArtProfileParserBuilder> parserBuilderConsumer) {
+      HumanReadableArtProfileParser.Builder parserBuilder =
+          HumanReadableArtProfileParser.builder().setReporter(reporter).setProfileBuilder(this);
+      parserBuilderConsumer.accept(parserBuilder);
+      HumanReadableArtProfileParser parser = parserBuilder.build();
+      parser.parse(textInputStream, artProfileProvider.getOrigin());
+      return this;
+    }
+
+    public ArtProfile build() {
+      return new ArtProfile(rules);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileBuilder.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileBuilder.java
index fbe9d78..57a7856 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileBuilder.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileBuilder.java
@@ -4,12 +4,27 @@
 
 package com.android.tools.r8.profile.art;
 
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.TextInputStream;
+import java.util.function.Consumer;
 
+/** API for building an ART profile. */
+@Keep
 public interface ArtProfileBuilder {
 
-  void addClassRule(ClassReference classReference, ArtProfileClassRuleInfo classRuleInfo);
+  /** API for adding information about a class rule to the compiler. */
+  ArtProfileBuilder addClassRule(Consumer<ArtProfileClassRuleBuilder> classRuleBuilderConsumer);
 
-  void addMethodRule(MethodReference methodReference, ArtProfileMethodRuleInfo methodRuleInfo);
+  /** API for adding information about a method rule to the compiler. */
+  ArtProfileBuilder addMethodRule(Consumer<ArtProfileMethodRuleBuilder> methodRuleBuilderConsumer);
+
+  /**
+   * Adds the rules from the given human-readable ART profile and then closes the stream.
+   *
+   * @see <a href="https://developer.android.com/topic/performance/baselineprofiles">ART Baseline
+   *     Profiles</a>
+   */
+  ArtProfileBuilder addHumanReadableArtProfile(
+      TextInputStream textInputStream,
+      Consumer<HumanReadableArtProfileParserBuilder> parserBuilderConsumer);
 }
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileBuilderUtils.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileBuilderUtils.java
index 3426e7b..77d0005 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileBuilderUtils.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileBuilderUtils.java
@@ -7,10 +7,16 @@
 import static com.android.tools.r8.synthesis.SyntheticNaming.COMPANION_CLASS_SUFFIX;
 import static com.android.tools.r8.synthesis.SyntheticNaming.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR;
 
+import com.android.tools.r8.TextInputStream;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.startup.StartupProfileBuilder;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.function.Consumer;
 
 public class ArtProfileBuilderUtils {
 
@@ -67,47 +73,120 @@
    */
   public static ArtProfileBuilder createBuilderForArtProfileToStartupProfileConversion(
       StartupProfileBuilder startupProfileBuilder,
-      ArtProfileRulePredicate rulePredicate,
       SyntheticToSyntheticContextGeneralization syntheticToSyntheticContextGeneralization) {
     return new ArtProfileBuilder() {
 
       @Override
-      public void addClassRule(
-          ClassReference classReference, ArtProfileClassRuleInfo classRuleInfo) {
-        if (rulePredicate.testClassRule(classReference, classRuleInfo)) {
-          ClassReference syntheticContextReference =
-              syntheticToSyntheticContextGeneralization.getSyntheticContextReference(
-                  classReference);
-          if (syntheticContextReference == null) {
-            startupProfileBuilder.addStartupClass(
-                startupClassBuilder -> startupClassBuilder.setClassReference(classReference));
-          } else {
-            startupProfileBuilder.addSyntheticStartupMethod(
-                syntheticStartupMethodBuilder ->
-                    syntheticStartupMethodBuilder.setSyntheticContextReference(
-                        syntheticContextReference));
-          }
+      public ArtProfileBuilder addClassRule(
+          Consumer<ArtProfileClassRuleBuilder> classRuleBuilderConsumer) {
+        MutableArtProfileClassRule classRule = new MutableArtProfileClassRule();
+        classRuleBuilderConsumer.accept(classRule);
+        ClassReference syntheticContextReference =
+            syntheticToSyntheticContextGeneralization.getSyntheticContextReference(
+                classRule.getClassReference());
+        if (syntheticContextReference == null) {
+          startupProfileBuilder.addStartupClass(
+              startupClassBuilder ->
+                  startupClassBuilder.setClassReference(classRule.getClassReference()));
+        } else {
+          startupProfileBuilder.addSyntheticStartupMethod(
+              syntheticStartupMethodBuilder ->
+                  syntheticStartupMethodBuilder.setSyntheticContextReference(
+                      syntheticContextReference));
         }
+        return this;
       }
 
       @Override
-      public void addMethodRule(
-          MethodReference methodReference, ArtProfileMethodRuleInfo methodRuleInfo) {
-        if (rulePredicate.testMethodRule(methodReference, methodRuleInfo)) {
-          ClassReference syntheticContextReference =
-              syntheticToSyntheticContextGeneralization.getSyntheticContextReference(
-                  methodReference.getHolderClass());
-          if (syntheticContextReference == null) {
-            startupProfileBuilder.addStartupMethod(
-                startupMethodBuilder -> startupMethodBuilder.setMethodReference(methodReference));
-          } else {
-            startupProfileBuilder.addSyntheticStartupMethod(
-                syntheticStartupMethodBuilder ->
-                    syntheticStartupMethodBuilder.setSyntheticContextReference(
-                        syntheticContextReference));
-          }
+      public ArtProfileBuilder addMethodRule(
+          Consumer<ArtProfileMethodRuleBuilder> methodRuleBuilderConsumer) {
+        MutableArtProfileMethodRule methodRule = new MutableArtProfileMethodRule();
+        methodRuleBuilderConsumer.accept(methodRule);
+        ClassReference syntheticContextReference =
+            syntheticToSyntheticContextGeneralization.getSyntheticContextReference(
+                methodRule.getMethodReference().getHolderClass());
+        if (syntheticContextReference == null) {
+          startupProfileBuilder.addStartupMethod(
+              startupMethodBuilder ->
+                  startupMethodBuilder.setMethodReference(methodRule.getMethodReference()));
+        } else {
+          startupProfileBuilder.addSyntheticStartupMethod(
+              syntheticStartupMethodBuilder ->
+                  syntheticStartupMethodBuilder.setSyntheticContextReference(
+                      syntheticContextReference));
         }
+        return this;
+      }
+
+      @Override
+      public ArtProfileBuilder addHumanReadableArtProfile(
+          TextInputStream textInputStream,
+          Consumer<HumanReadableArtProfileParserBuilder> parserBuilderConsumer) {
+        // The ART profile parser never calls addHumanReadableArtProfile().
+        throw new Unreachable();
       }
     };
   }
+
+  static class MutableArtProfileClassRule implements ArtProfileClassRuleBuilder {
+
+    private ClassReference classReference;
+
+    MutableArtProfileClassRule() {}
+
+    public ClassReference getClassReference() {
+      return classReference;
+    }
+
+    @Override
+    public ArtProfileClassRuleBuilder setClassReference(ClassReference classReference) {
+      this.classReference = classReference;
+      return this;
+    }
+
+    public ArtProfileClassRuleInfo getClassRuleInfo() {
+      return ArtProfileClassRuleInfoImpl.empty();
+    }
+
+    public void writeHumanReadableRuleString(OutputStreamWriter writer) throws IOException {
+      writer.write(classReference.getDescriptor());
+    }
+  }
+
+  static class MutableArtProfileMethodRule implements ArtProfileMethodRuleBuilder {
+
+    private MethodReference methodReference;
+    private ArtProfileMethodRuleInfoImpl methodRuleInfo = ArtProfileMethodRuleInfoImpl.empty();
+
+    MutableArtProfileMethodRule() {}
+
+    public MethodReference getMethodReference() {
+      return methodReference;
+    }
+
+    public ArtProfileMethodRuleInfo getMethodRuleInfo() {
+      return methodRuleInfo;
+    }
+
+    @Override
+    public ArtProfileMethodRuleBuilder setMethodReference(MethodReference methodReference) {
+      this.methodReference = methodReference;
+      return this;
+    }
+
+    @Override
+    public ArtProfileMethodRuleBuilder setMethodRuleInfo(
+        Consumer<ArtProfileMethodRuleInfoBuilder> methodRuleInfoBuilderConsumer) {
+      ArtProfileMethodRuleInfoImpl.Builder methodRuleInfoBuilder =
+          ArtProfileMethodRuleInfoImpl.builder();
+      methodRuleInfoBuilderConsumer.accept(methodRuleInfoBuilder);
+      methodRuleInfo = methodRuleInfoBuilder.build();
+      return this;
+    }
+
+    public void writeHumanReadableRuleString(OutputStreamWriter writer) throws IOException {
+      methodRuleInfo.writeHumanReadableFlags(writer);
+      writer.write(MethodReferenceUtils.toSmaliString(methodReference));
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRule.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRule.java
new file mode 100644
index 0000000..932ecb8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRule.java
@@ -0,0 +1,126 @@
+// Copyright (c) 2022, 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.profile.art;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.function.Consumer;
+
+public class ArtProfileClassRule extends ArtProfileRule {
+
+  private final DexType type;
+
+  ArtProfileClassRule(DexType type) {
+    this.type = type;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static Builder builder(DexItemFactory dexItemFactory) {
+    return new Builder(dexItemFactory);
+  }
+
+  @Override
+  public void accept(
+      Consumer<ArtProfileClassRule> classRuleConsumer,
+      Consumer<ArtProfileMethodRule> methodRuleConsumer) {
+    classRuleConsumer.accept(this);
+  }
+
+  public ClassReference getClassReference() {
+    return Reference.classFromDescriptor(type.toDescriptorString());
+  }
+
+  public ArtProfileClassRuleInfo getClassRuleInfo() {
+    return ArtProfileClassRuleInfoImpl.empty();
+  }
+
+  public DexType getType() {
+    return type;
+  }
+
+  @Override
+  public boolean isClassRule() {
+    return true;
+  }
+
+  @Override
+  public ArtProfileClassRule asClassRule() {
+    return this;
+  }
+
+  @Override
+  public void writeHumanReadableRuleString(OutputStreamWriter writer) throws IOException {
+    writer.write(type.toDescriptorString());
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    ArtProfileClassRule that = (ArtProfileClassRule) o;
+    return type == that.type;
+  }
+
+  @Override
+  public int hashCode() {
+    return type.hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return type.toDescriptorString();
+  }
+
+  public static class Builder extends ArtProfileRule.Builder implements ArtProfileClassRuleBuilder {
+
+    private final DexItemFactory dexItemFactory;
+    private DexType type;
+
+    Builder() {
+      this(null);
+    }
+
+    Builder(DexItemFactory dexItemFactory) {
+      this.dexItemFactory = dexItemFactory;
+    }
+
+    @Override
+    public boolean isClassRuleBuilder() {
+      return true;
+    }
+
+    @Override
+    Builder asClassRuleBuilder() {
+      return this;
+    }
+
+    @Override
+    public Builder setClassReference(ClassReference classReference) {
+      assert dexItemFactory != null;
+      return setType(dexItemFactory.createType(classReference.getDescriptor()));
+    }
+
+    public Builder setType(DexType type) {
+      this.type = type;
+      return this;
+    }
+
+    @Override
+    public ArtProfileClassRule build() {
+      return new ArtProfileClassRule(type);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRuleBuilder.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRuleBuilder.java
new file mode 100644
index 0000000..eaeab93
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRuleBuilder.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2022, 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.profile.art;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.ClassReference;
+
+/** API for defining a class rule for an ART profile. */
+@Keep
+public interface ArtProfileClassRuleBuilder {
+
+  ArtProfileClassRuleBuilder setClassReference(ClassReference classReference);
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRuleInfoImpl.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRuleInfoImpl.java
index 7be8869..9fa3001 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRuleInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRuleInfoImpl.java
@@ -13,4 +13,14 @@
   public static ArtProfileClassRuleInfoImpl empty() {
     return INSTANCE;
   }
+
+  @Override
+  public boolean equals(Object obj) {
+    return this == obj;
+  }
+
+  @Override
+  public int hashCode() {
+    return System.identityHashCode(this);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java
new file mode 100644
index 0000000..3a885d9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2022, 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.profile.art;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public abstract class ArtProfileCollection {
+
+  public static ArtProfileCollection createInitialArtProfileCollection(InternalOptions options) {
+    ArtProfileOptions artProfileOptions = options.getArtProfileOptions();
+    Collection<ArtProfileForRewriting> artProfilesForRewriting =
+        artProfileOptions.getArtProfilesForRewriting();
+    if (artProfilesForRewriting.isEmpty()) {
+      return empty();
+    }
+    if (artProfileOptions.isPassthrough()) {
+      return passthrough();
+    }
+    List<ArtProfile> artProfiles = new ArrayList<>(artProfilesForRewriting.size());
+    for (ArtProfileForRewriting artProfileForRewriting :
+        options.getArtProfileOptions().getArtProfilesForRewriting()) {
+      ArtProfileProvider artProfileProvider = artProfileForRewriting.getArtProfileProvider();
+      ArtProfile.Builder artProfileBuilder = ArtProfile.builder(artProfileProvider, options);
+      artProfileForRewriting.getArtProfileProvider().getArtProfile(artProfileBuilder);
+      artProfiles.add(artProfileBuilder.build());
+    }
+    return new NonEmptyArtProfileCollection(artProfiles);
+  }
+
+  public static EmptyArtProfileCollection empty() {
+    return EmptyArtProfileCollection.getInstance();
+  }
+
+  public static PassthroughArtProfileCollection passthrough() {
+    return PassthroughArtProfileCollection.getInstance();
+  }
+
+  public abstract ArtProfileCollection rewrittenWithLens(GraphLens lens);
+
+  public abstract ArtProfileCollection rewrittenWithLens(
+      NamingLens lens, DexItemFactory dexItemFactory);
+
+  public abstract void supplyConsumers(AppView<?> appView);
+
+  public abstract ArtProfileCollection withoutPrunedItems(PrunedItems prunedItems);
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileConsumer.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileConsumer.java
new file mode 100644
index 0000000..b395a12
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileConsumer.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2022, 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.profile.art;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.TextOutputStream;
+
+/**
+ * API for consuming an ART profile provided by the compiler, which has been rewritten to match the
+ * residual, optimized app.
+ */
+@Keep
+public interface ArtProfileConsumer {
+
+  /**
+   * Returns a {@link TextOutputStream} that will receive the rules of the residual ART profile in
+   * the human-readable ART baseline profile format. This may return <code>null</code> to specify
+   * that the compiler should not emit the residual ART profile in the human-readable ART baseline
+   * profile format.
+   *
+   * @see <a href="https://developer.android.com/topic/performance/baselineprofiles">ART Baseline
+   *     Profiles</a>
+   */
+  default TextOutputStream getHumanReadableArtProfileConsumer() {
+    return null;
+  }
+
+  /**
+   * Returns an {@link ArtProfileRuleConsumer} that will receive the rules of the residual ART
+   * profile. If this returns <code>null</code> no rules will be emitted.
+   */
+  default ArtProfileRuleConsumer getRuleConsumer() {
+    return null;
+  }
+
+  /**
+   * Callback signifying that all rules of the residual ART profile have been emitted to the rule
+   * consumer.
+   *
+   * @param handler Diagnostics handler for reporting.
+   */
+  void finished(DiagnosticsHandler handler);
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileForRewriting.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileForRewriting.java
new file mode 100644
index 0000000..7065f86
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileForRewriting.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2022, 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.profile.art;
+
+/** Internal pair of an {@link ArtProfileProvider} and {@link ArtProfileConsumer}. */
+public class ArtProfileForRewriting {
+
+  private final ArtProfileProvider artProfileProvider;
+  private final ArtProfileConsumer residualArtProfileConsumer;
+
+  public ArtProfileForRewriting(
+      ArtProfileProvider artProfileProvider, ArtProfileConsumer residualArtProfileConsumer) {
+    this.artProfileProvider = artProfileProvider;
+    this.residualArtProfileConsumer = residualArtProfileConsumer;
+  }
+
+  /** Specifies a provider that performs callbacks to a given {@link ArtProfileBuilder}. */
+  public ArtProfileProvider getArtProfileProvider() {
+    return artProfileProvider;
+  }
+
+  /**
+   * Specifies a consumer that should receive the ART profile after it has been rewritten to match
+   * the residual, optimized application.
+   */
+  public ArtProfileConsumer getResidualArtProfileConsumer() {
+    return residualArtProfileConsumer;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRule.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRule.java
new file mode 100644
index 0000000..f9380b8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRule.java
@@ -0,0 +1,148 @@
+// Copyright (c) 2022, 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.profile.art;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.function.Consumer;
+
+public class ArtProfileMethodRule extends ArtProfileRule {
+
+  private final DexMethod method;
+  private final ArtProfileMethodRuleInfoImpl info;
+
+  ArtProfileMethodRule(DexMethod method, ArtProfileMethodRuleInfoImpl info) {
+    this.method = method;
+    this.info = info;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static Builder builder(DexItemFactory dexItemFactory) {
+    return new Builder(dexItemFactory);
+  }
+
+  @Override
+  public void accept(
+      Consumer<ArtProfileClassRule> classRuleConsumer,
+      Consumer<ArtProfileMethodRule> methodRuleConsumer) {
+    methodRuleConsumer.accept(this);
+  }
+
+  public DexMethod getMethod() {
+    return method;
+  }
+
+  public MethodReference getMethodReference() {
+    return method.asMethodReference();
+  }
+
+  public ArtProfileMethodRuleInfo getMethodRuleInfo() {
+    return info;
+  }
+
+  @Override
+  public boolean isMethodRule() {
+    return true;
+  }
+
+  @Override
+  public ArtProfileMethodRule asMethodRule() {
+    return this;
+  }
+
+  @Override
+  public void writeHumanReadableRuleString(OutputStreamWriter writer) throws IOException {
+    info.writeHumanReadableFlags(writer);
+    writer.write(method.toSmaliString());
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    ArtProfileMethodRule that = (ArtProfileMethodRule) o;
+    return method.equals(that.method) && info.equals(that.info);
+  }
+
+  @Override
+  public int hashCode() {
+    // A profile does not have two rules with the same reference but different flags, so no need to
+    // include the flags in the hash.
+    return method.hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return info.toString() + method.toSmaliString();
+  }
+
+  public static class Builder extends ArtProfileRule.Builder
+      implements ArtProfileMethodRuleBuilder {
+
+    private final DexItemFactory dexItemFactory;
+
+    private DexMethod method;
+    private ArtProfileMethodRuleInfoImpl.Builder methodRuleInfoBuilder =
+        ArtProfileMethodRuleInfoImpl.builder();
+
+    Builder() {
+      this(null);
+    }
+
+    Builder(DexItemFactory dexItemFactory) {
+      this.dexItemFactory = dexItemFactory;
+    }
+
+    @Override
+    public boolean isMethodRuleBuilder() {
+      return true;
+    }
+
+    @Override
+    Builder asMethodRuleBuilder() {
+      return this;
+    }
+
+    @Override
+    public Builder setMethodReference(MethodReference methodReference) {
+      assert dexItemFactory != null;
+      return setMethod(MethodReferenceUtils.toDexMethod(methodReference, dexItemFactory));
+    }
+
+    public Builder setMethod(DexMethod method) {
+      this.method = method;
+      return this;
+    }
+
+    @Override
+    public Builder setMethodRuleInfo(
+        Consumer<ArtProfileMethodRuleInfoBuilder> methodRuleInfoBuilderConsumer) {
+      methodRuleInfoBuilder.clear();
+      return acceptMethodRuleInfoBuilder(methodRuleInfoBuilderConsumer);
+    }
+
+    public Builder acceptMethodRuleInfoBuilder(
+        Consumer<? super ArtProfileMethodRuleInfoImpl.Builder> methodRuleInfoBuilderConsumer) {
+      methodRuleInfoBuilderConsumer.accept(methodRuleInfoBuilder);
+      return this;
+    }
+
+    @Override
+    public ArtProfileMethodRule build() {
+      return new ArtProfileMethodRule(method, methodRuleInfoBuilder.build());
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleBuilder.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleBuilder.java
new file mode 100644
index 0000000..4bae3b3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleBuilder.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2022, 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.profile.art;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.MethodReference;
+import java.util.function.Consumer;
+
+/** API for defining a method rule for an ART profile. */
+@Keep
+public interface ArtProfileMethodRuleBuilder {
+
+  ArtProfileMethodRuleBuilder setMethodReference(MethodReference methodReference);
+
+  ArtProfileMethodRuleBuilder setMethodRuleInfo(
+      Consumer<ArtProfileMethodRuleInfoBuilder> methodRuleInfoBuilderConsumer);
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleFlagsUtils.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleFlagsUtils.java
new file mode 100644
index 0000000..31f9b10
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleFlagsUtils.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2022, 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.profile.art;
+
+public class ArtProfileMethodRuleFlagsUtils {
+
+  private static final int FLAG_HOT = 1;
+  private static final int FLAG_STARTUP = 2;
+  private static final int FLAG_POST_STARTUP = 4;
+
+  // Getters.
+
+  public static boolean isHot(int flags) {
+    return isFlagSet(flags, FLAG_HOT);
+  }
+
+  public static boolean isStartup(int flags) {
+    return isFlagSet(flags, FLAG_STARTUP);
+  }
+
+  public static boolean isPostStartup(int flags) {
+    return isFlagSet(flags, FLAG_POST_STARTUP);
+  }
+
+  private static boolean isFlagSet(int flags, int flag) {
+    return (flags & flag) != 0;
+  }
+
+  // Setters.
+
+  public static int setIsHot(int flags, boolean isHot) {
+    return isHot ? setFlag(flags, FLAG_HOT) : unsetFlag(flags, FLAG_HOT);
+  }
+
+  public static int setIsStartup(int flags, boolean isStartup) {
+    return isStartup ? setFlag(flags, FLAG_STARTUP) : unsetFlag(flags, FLAG_STARTUP);
+  }
+
+  public static int setIsPostStartup(int flags, boolean isPostStartup) {
+    return isPostStartup ? setFlag(flags, FLAG_POST_STARTUP) : unsetFlag(flags, FLAG_POST_STARTUP);
+  }
+
+  private static int setFlag(int flags, int flag) {
+    return flags | flag;
+  }
+
+  private static int unsetFlag(int flags, int flag) {
+    return flags & ~flag;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleInfoBuilder.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleInfoBuilder.java
new file mode 100644
index 0000000..11f8b42
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleInfoBuilder.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2022, 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.profile.art;
+
+import com.android.tools.r8.Keep;
+
+/** API for providing metadata related to a method rule for an ART profile. */
+@Keep
+public interface ArtProfileMethodRuleInfoBuilder {
+
+  ArtProfileMethodRuleInfoBuilder setIsHot(boolean isHot);
+
+  ArtProfileMethodRuleInfoBuilder setIsStartup(boolean isStartup);
+
+  ArtProfileMethodRuleInfoBuilder setIsPostStartup(boolean isPostStartup);
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleInfoImpl.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleInfoImpl.java
index ca2d241..86d4c84 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleInfoImpl.java
@@ -4,11 +4,12 @@
 
 package com.android.tools.r8.profile.art;
 
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+
 public class ArtProfileMethodRuleInfoImpl implements ArtProfileMethodRuleInfo {
 
-  private static final int FLAG_HOT = 1;
-  private static final int FLAG_STARTUP = 2;
-  private static final int FLAG_POST_STARTUP = 4;
+  private static final ArtProfileMethodRuleInfoImpl EMPTY = new ArtProfileMethodRuleInfoImpl(0);
 
   private final int flags;
 
@@ -20,41 +21,122 @@
     return new Builder();
   }
 
+  public static ArtProfileMethodRuleInfoImpl empty() {
+    return EMPTY;
+  }
+
   public boolean isEmpty() {
     return flags == 0;
   }
 
   @Override
   public boolean isHot() {
-    return (flags & FLAG_HOT) != 0;
+    return ArtProfileMethodRuleFlagsUtils.isHot(flags);
   }
 
   @Override
   public boolean isStartup() {
-    return (flags & FLAG_STARTUP) != 0;
+    return ArtProfileMethodRuleFlagsUtils.isStartup(flags);
   }
 
   @Override
   public boolean isPostStartup() {
-    return (flags & FLAG_POST_STARTUP) != 0;
+    return ArtProfileMethodRuleFlagsUtils.isPostStartup(flags);
   }
 
-  public static class Builder {
+  public void writeHumanReadableFlags(OutputStreamWriter writer) throws IOException {
+    if (isHot()) {
+      writer.write('H');
+    }
+    if (isStartup()) {
+      writer.write('S');
+    }
+    if (isPostStartup()) {
+      writer.write('P');
+    }
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    ArtProfileMethodRuleInfoImpl that = (ArtProfileMethodRuleInfoImpl) o;
+    return flags == that.flags;
+  }
+
+  @Override
+  public int hashCode() {
+    return flags;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder builder = new StringBuilder();
+    if (isHot()) {
+      builder.append('H');
+    }
+    if (isStartup()) {
+      builder.append('S');
+    }
+    if (isPostStartup()) {
+      builder.append('P');
+    }
+    return builder.toString();
+  }
+
+  public static class Builder implements ArtProfileMethodRuleInfoBuilder {
 
     private int flags;
 
-    public Builder setHot() {
-      flags |= FLAG_HOT;
+    Builder clear() {
+      flags = 0;
       return this;
     }
 
-    public Builder setStartup() {
-      flags |= FLAG_STARTUP;
+    public Builder merge(ArtProfileMethodRuleInfo methodRuleInfo) {
+      if (methodRuleInfo.isHot()) {
+        setIsHot();
+      }
+      if (methodRuleInfo.isStartup()) {
+        setIsStartup();
+      }
+      if (methodRuleInfo.isPostStartup()) {
+        setIsPostStartup();
+      }
       return this;
     }
 
-    public Builder setPostStartup() {
-      flags |= FLAG_POST_STARTUP;
+    public Builder setIsHot() {
+      return setIsHot(true);
+    }
+
+    @Override
+    public Builder setIsHot(boolean isHot) {
+      flags = ArtProfileMethodRuleFlagsUtils.setIsHot(flags, isHot);
+      return this;
+    }
+
+    public Builder setIsStartup() {
+      return setIsStartup(true);
+    }
+
+    @Override
+    public Builder setIsStartup(boolean isStartup) {
+      flags = ArtProfileMethodRuleFlagsUtils.setIsStartup(flags, isStartup);
+      return this;
+    }
+
+    public Builder setIsPostStartup() {
+      return setIsPostStartup(true);
+    }
+
+    @Override
+    public Builder setIsPostStartup(boolean isPostStartup) {
+      flags = ArtProfileMethodRuleFlagsUtils.setIsPostStartup(flags, isPostStartup);
       return this;
     }
 
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
new file mode 100644
index 0000000..231a2d6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2022, 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.profile.art;
+
+import java.util.Collection;
+import java.util.Collections;
+
+public class ArtProfileOptions {
+
+  private Collection<ArtProfileForRewriting> artProfilesForRewriting = Collections.emptyList();
+  private boolean passthrough;
+
+  public ArtProfileOptions() {}
+
+  public Collection<ArtProfileForRewriting> getArtProfilesForRewriting() {
+    return artProfilesForRewriting;
+  }
+
+  public ArtProfileOptions setArtProfilesForRewriting(Collection<ArtProfileForRewriting> inputs) {
+    this.artProfilesForRewriting = inputs;
+    return this;
+  }
+
+  public boolean isPassthrough() {
+    return passthrough;
+  }
+
+  public ArtProfileOptions setPassthrough(boolean passthrough) {
+    this.passthrough = passthrough;
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileProvider.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileProvider.java
new file mode 100644
index 0000000..87450e5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileProvider.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2022, 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.profile.art;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.Resource;
+
+/** API for providing an ART profile to the compiler. */
+@Keep
+public interface ArtProfileProvider extends Resource {
+
+  void getArtProfile(ArtProfileBuilder profileBuilder);
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileRule.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileRule.java
new file mode 100644
index 0000000..f05167c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileRule.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2022, 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.profile.art;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.function.Consumer;
+
+public abstract class ArtProfileRule {
+
+  public abstract void accept(
+      Consumer<ArtProfileClassRule> classRuleConsumer,
+      Consumer<ArtProfileMethodRule> methodRuleConsumer);
+
+  public boolean isClassRule() {
+    return false;
+  }
+
+  public ArtProfileClassRule asClassRule() {
+    return null;
+  }
+
+  public boolean isMethodRule() {
+    return false;
+  }
+
+  public ArtProfileMethodRule asMethodRule() {
+    return null;
+  }
+
+  public abstract void writeHumanReadableRuleString(OutputStreamWriter writer) throws IOException;
+
+  public abstract static class Builder {
+
+    public boolean isClassRuleBuilder() {
+      return false;
+    }
+
+    ArtProfileClassRule.Builder asClassRuleBuilder() {
+      return null;
+    }
+
+    public boolean isMethodRuleBuilder() {
+      return false;
+    }
+
+    ArtProfileMethodRule.Builder asMethodRuleBuilder() {
+      return null;
+    }
+
+    public abstract ArtProfileRule build();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileRuleConsumer.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileRuleConsumer.java
new file mode 100644
index 0000000..beab144
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileRuleConsumer.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2022, 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.profile.art;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+
+/**
+ * API for consuming rules from an ART profile. The compiler will call the methods {@link
+ * #acceptClassRule} and {@link #acceptMethodRule} for each class and method rule (respectively) in
+ * the profile.
+ */
+@Keep
+public interface ArtProfileRuleConsumer {
+
+  /** Provides information about a specific class rule to the consumer. */
+  void acceptClassRule(ClassReference classReference, ArtProfileClassRuleInfo classRuleInfo);
+
+  /** Provides information about a specific method rule to the consumer. */
+  void acceptMethodRule(MethodReference methodReference, ArtProfileMethodRuleInfo methodRuleInfo);
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java
new file mode 100644
index 0000000..f0313ef
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2022, 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.profile.art;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.naming.NamingLens;
+
+public class EmptyArtProfileCollection extends ArtProfileCollection {
+
+  private static final EmptyArtProfileCollection INSTANCE = new EmptyArtProfileCollection();
+
+  private EmptyArtProfileCollection() {}
+
+  static EmptyArtProfileCollection getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public ArtProfileCollection rewrittenWithLens(GraphLens lens) {
+    return this;
+  }
+
+  @Override
+  public ArtProfileCollection rewrittenWithLens(NamingLens lens, DexItemFactory dexItemFactory) {
+    return this;
+  }
+
+  @Override
+  public void supplyConsumers(AppView<?> appView) {
+    assert appView.options().getArtProfileOptions().getArtProfilesForRewriting().isEmpty();
+  }
+
+  @Override
+  public ArtProfileCollection withoutPrunedItems(PrunedItems prunedItems) {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileConsumer.java b/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileConsumer.java
new file mode 100644
index 0000000..2b18ce6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileConsumer.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2022, 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.profile.art;
+
+import com.android.tools.r8.DiagnosticsHandler;
+
+public class EmptyArtProfileConsumer implements ArtProfileConsumer {
+
+  private static final EmptyArtProfileConsumer INSTANCE = new EmptyArtProfileConsumer();
+
+  private EmptyArtProfileConsumer() {}
+
+  public static EmptyArtProfileConsumer getInstance() {
+    return INSTANCE;
+  }
+
+  public static ArtProfileConsumer orEmpty(ArtProfileConsumer artProfileConsumer) {
+    return artProfileConsumer != null ? artProfileConsumer : getInstance();
+  }
+
+  @Override
+  public ArtProfileRuleConsumer getRuleConsumer() {
+    return EmptyArtProfileRuleConsumer.getInstance();
+  }
+
+  @Override
+  public void finished(DiagnosticsHandler handler) {
+    // Intentionally empty.
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileRuleConsumer.java b/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileRuleConsumer.java
new file mode 100644
index 0000000..77d520f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileRuleConsumer.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2022, 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.profile.art;
+
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+
+public class EmptyArtProfileRuleConsumer implements ArtProfileRuleConsumer {
+
+  private static final EmptyArtProfileRuleConsumer INSTANCE = new EmptyArtProfileRuleConsumer();
+
+  private EmptyArtProfileRuleConsumer() {}
+
+  public static EmptyArtProfileRuleConsumer getInstance() {
+    return INSTANCE;
+  }
+
+  public static ArtProfileRuleConsumer orEmpty(ArtProfileRuleConsumer ruleConsumer) {
+    return ruleConsumer != null ? ruleConsumer : getInstance();
+  }
+
+  @Override
+  public void acceptClassRule(
+      ClassReference classReference, ArtProfileClassRuleInfo classRuleInfo) {
+    // Intentionally empty.
+  }
+
+  @Override
+  public void acceptMethodRule(
+      MethodReference methodReference, ArtProfileMethodRuleInfo methodRuleInfo) {
+    // Intentionally empty.
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/HumanReadableArtProfileParser.java b/src/main/java/com/android/tools/r8/profile/art/HumanReadableArtProfileParser.java
index 5f0c92b..3abbbca 100644
--- a/src/main/java/com/android/tools/r8/profile/art/HumanReadableArtProfileParser.java
+++ b/src/main/java/com/android/tools/r8/profile/art/HumanReadableArtProfileParser.java
@@ -21,10 +21,13 @@
 public class HumanReadableArtProfileParser {
 
   private final ArtProfileBuilder profileBuilder;
+  private final ArtProfileRulePredicate rulePredicate;
   private final Reporter reporter;
 
-  HumanReadableArtProfileParser(ArtProfileBuilder profileBuilder, Reporter reporter) {
+  HumanReadableArtProfileParser(
+      ArtProfileBuilder profileBuilder, ArtProfileRulePredicate rulePredicate, Reporter reporter) {
     this.profileBuilder = profileBuilder;
+    this.rulePredicate = rulePredicate;
     this.reporter = reporter;
   }
 
@@ -64,9 +67,9 @@
   public boolean parseRule(String rule) {
     ArtProfileMethodRuleInfoImpl.Builder methodRuleInfoBuilder =
         ArtProfileMethodRuleInfoImpl.builder();
-    rule = parseFlag(rule, 'H', methodRuleInfoBuilder::setHot);
-    rule = parseFlag(rule, 'S', methodRuleInfoBuilder::setStartup);
-    rule = parseFlag(rule, 'P', methodRuleInfoBuilder::setPostStartup);
+    rule = parseFlag(rule, 'H', methodRuleInfoBuilder::setIsHot);
+    rule = parseFlag(rule, 'S', methodRuleInfoBuilder::setIsStartup);
+    rule = parseFlag(rule, 'P', methodRuleInfoBuilder::setIsPostStartup);
     return parseClassOrMethodDescriptor(rule, methodRuleInfoBuilder.build());
   }
 
@@ -95,7 +98,10 @@
     if (classReference == null) {
       return false;
     }
-    profileBuilder.addClassRule(classReference, ArtProfileClassRuleInfoImpl.empty());
+    if (rulePredicate.testClassRule(classReference, ArtProfileClassRuleInfoImpl.empty())) {
+      profileBuilder.addClassRule(
+          classRuleBuilder -> classRuleBuilder.setClassReference(classReference));
+    }
     return true;
   }
 
@@ -106,13 +112,25 @@
     if (methodReference == null) {
       return false;
     }
-    profileBuilder.addMethodRule(methodReference, methodRuleInfo);
+    if (rulePredicate.testMethodRule(methodReference, methodRuleInfo)) {
+      profileBuilder.addMethodRule(
+          methodRuleBuilder ->
+              methodRuleBuilder
+                  .setMethodReference(methodReference)
+                  .setMethodRuleInfo(
+                      methodRuleInfoBuilder ->
+                          methodRuleInfoBuilder
+                              .setIsHot(methodRuleInfo.isHot())
+                              .setIsStartup(methodRuleInfo.isStartup())
+                              .setIsPostStartup(methodRuleInfo.isPostStartup())));
+    }
     return true;
   }
 
-  public static class Builder {
+  public static class Builder implements HumanReadableArtProfileParserBuilder {
 
     private ArtProfileBuilder profileBuilder;
+    private ArtProfileRulePredicate rulePredicate = new AlwaysTrueArtProfileRulePredicate();
     private Reporter reporter;
 
     public Builder setReporter(Reporter reporter) {
@@ -120,13 +138,19 @@
       return this;
     }
 
+    @Override
+    public Builder setRulePredicate(ArtProfileRulePredicate rulePredicate) {
+      this.rulePredicate = rulePredicate;
+      return this;
+    }
+
     public Builder setProfileBuilder(ArtProfileBuilder profileBuilder) {
       this.profileBuilder = profileBuilder;
       return this;
     }
 
     public HumanReadableArtProfileParser build() {
-      return new HumanReadableArtProfileParser(profileBuilder, reporter);
+      return new HumanReadableArtProfileParser(profileBuilder, rulePredicate, reporter);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java
new file mode 100644
index 0000000..e3cbaf7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2022, 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.profile.art;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.function.Function;
+
+public class NonEmptyArtProfileCollection extends ArtProfileCollection {
+
+  private final Collection<ArtProfile> artProfiles;
+
+  NonEmptyArtProfileCollection(Collection<ArtProfile> artProfiles) {
+    this.artProfiles = artProfiles;
+  }
+
+  @Override
+  public NonEmptyArtProfileCollection rewrittenWithLens(GraphLens lens) {
+    return map(artProfile -> artProfile.rewrittenWithLens(lens));
+  }
+
+  @Override
+  public NonEmptyArtProfileCollection rewrittenWithLens(
+      NamingLens lens, DexItemFactory dexItemFactory) {
+    assert !lens.isIdentityLens();
+    return map(artProfile -> artProfile.rewrittenWithLens(lens, dexItemFactory));
+  }
+
+  @Override
+  public void supplyConsumers(AppView<?> appView) {
+    NonEmptyArtProfileCollection collection =
+        appView.getNamingLens().isIdentityLens()
+            ? this
+            : rewrittenWithLens(appView.getNamingLens(), appView.dexItemFactory());
+    InternalOptions options = appView.options();
+    Collection<ArtProfileForRewriting> inputs =
+        options.getArtProfileOptions().getArtProfilesForRewriting();
+    assert !inputs.isEmpty();
+    assert collection.artProfiles.size() == inputs.size();
+    Iterator<ArtProfileForRewriting> inputIterator = inputs.iterator();
+    for (ArtProfile artProfile : collection.artProfiles) {
+      ArtProfileForRewriting input = inputIterator.next();
+      artProfile.supplyConsumer(input.getResidualArtProfileConsumer(), options.reporter);
+    }
+  }
+
+  @Override
+  public NonEmptyArtProfileCollection withoutPrunedItems(PrunedItems prunedItems) {
+    return map(artProfile -> artProfile.withoutPrunedItems(prunedItems));
+  }
+
+  private NonEmptyArtProfileCollection map(Function<ArtProfile, ArtProfile> fn) {
+    ImmutableList.Builder<ArtProfile> newArtProfiles =
+        ImmutableList.builderWithExpectedSize(artProfiles.size());
+    for (ArtProfile artProfile : artProfiles) {
+      newArtProfiles.add(fn.apply(artProfile));
+    }
+    return new NonEmptyArtProfileCollection(newArtProfiles.build());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/PassthroughArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/PassthroughArtProfileCollection.java
new file mode 100644
index 0000000..2198da6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/PassthroughArtProfileCollection.java
@@ -0,0 +1,158 @@
+// Copyright (c) 2022, 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.profile.art;
+
+import com.android.tools.r8.TextInputStream;
+import com.android.tools.r8.TextOutputStream;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.profile.art.ArtProfileBuilderUtils.MutableArtProfileClassRule;
+import com.android.tools.r8.profile.art.ArtProfileBuilderUtils.MutableArtProfileMethodRule;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UncheckedIOException;
+import java.util.function.Consumer;
+
+public class PassthroughArtProfileCollection extends ArtProfileCollection {
+
+  private static final PassthroughArtProfileCollection INSTANCE =
+      new PassthroughArtProfileCollection();
+
+  private PassthroughArtProfileCollection() {}
+
+  static PassthroughArtProfileCollection getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public ArtProfileCollection rewrittenWithLens(GraphLens lens) {
+    return this;
+  }
+
+  @Override
+  public ArtProfileCollection rewrittenWithLens(NamingLens lens, DexItemFactory dexItemFactory) {
+    return this;
+  }
+
+  @Override
+  public void supplyConsumers(AppView<?> appView) {
+    for (ArtProfileForRewriting artProfileForRewriting :
+        appView.options().getArtProfileOptions().getArtProfilesForRewriting()) {
+      ArtProfileProvider artProfileProvider = artProfileForRewriting.getArtProfileProvider();
+      ArtProfileConsumer artProfileConsumer =
+          EmptyArtProfileConsumer.orEmpty(artProfileForRewriting.getResidualArtProfileConsumer());
+      supplyArtProfileConsumer(appView, artProfileConsumer, artProfileProvider);
+      artProfileConsumer.finished(appView.reporter());
+    }
+  }
+
+  private void supplyArtProfileConsumer(
+      AppView<?> appView,
+      ArtProfileConsumer artProfileConsumer,
+      ArtProfileProvider artProfileProvider) {
+    ArtProfileConsumerSupplier artProfileConsumerSupplier =
+        new ArtProfileConsumerSupplier(artProfileConsumer);
+    try {
+      ArtProfileRuleConsumer ruleConsumer =
+          EmptyArtProfileRuleConsumer.orEmpty(artProfileConsumer.getRuleConsumer());
+      artProfileProvider.getArtProfile(
+          new ArtProfileBuilder() {
+
+            @Override
+            public ArtProfileBuilder addClassRule(
+                Consumer<ArtProfileClassRuleBuilder> classRuleBuilderConsumer) {
+              MutableArtProfileClassRule classRule = new MutableArtProfileClassRule();
+              classRuleBuilderConsumer.accept(classRule);
+              ruleConsumer.acceptClassRule(
+                  classRule.getClassReference(), classRule.getClassRuleInfo());
+              artProfileConsumerSupplier.supply(classRule);
+              return this;
+            }
+
+            @Override
+            public ArtProfileBuilder addMethodRule(
+                Consumer<ArtProfileMethodRuleBuilder> methodRuleBuilderConsumer) {
+              MutableArtProfileMethodRule methodRule = new MutableArtProfileMethodRule();
+              methodRuleBuilderConsumer.accept(methodRule);
+              ruleConsumer.acceptMethodRule(
+                  methodRule.getMethodReference(), methodRule.getMethodRuleInfo());
+              artProfileConsumerSupplier.supply(methodRule);
+              return this;
+            }
+
+            @Override
+            public ArtProfileBuilder addHumanReadableArtProfile(
+                TextInputStream textInputStream,
+                Consumer<HumanReadableArtProfileParserBuilder> parserBuilderConsumer) {
+              HumanReadableArtProfileParser.Builder parserBuilder =
+                  HumanReadableArtProfileParser.builder()
+                      .setReporter(appView.reporter())
+                      .setProfileBuilder(this);
+              parserBuilderConsumer.accept(parserBuilder);
+              HumanReadableArtProfileParser parser = parserBuilder.build();
+              parser.parse(textInputStream, artProfileProvider.getOrigin());
+              return this;
+            }
+          });
+    } finally {
+      artProfileConsumerSupplier.close();
+    }
+  }
+
+  @Override
+  public ArtProfileCollection withoutPrunedItems(PrunedItems prunedItems) {
+    return this;
+  }
+
+  private static class ArtProfileConsumerSupplier {
+
+    private final OutputStreamWriter outputStreamWriter;
+
+    ArtProfileConsumerSupplier(ArtProfileConsumer artProfileConsumer) {
+      TextOutputStream textOutputStream = artProfileConsumer.getHumanReadableArtProfileConsumer();
+      this.outputStreamWriter =
+          textOutputStream != null
+              ? new OutputStreamWriter(
+                  textOutputStream.getOutputStream(), textOutputStream.getCharset())
+              : null;
+      ;
+    }
+
+    void supply(MutableArtProfileClassRule classRule) {
+      if (outputStreamWriter != null) {
+        try {
+          classRule.writeHumanReadableRuleString(outputStreamWriter);
+          outputStreamWriter.write('\n');
+        } catch (IOException e) {
+          throw new UncheckedIOException(e);
+        }
+      }
+    }
+
+    void supply(MutableArtProfileMethodRule methodRule) {
+      if (outputStreamWriter != null) {
+        try {
+          methodRule.writeHumanReadableRuleString(outputStreamWriter);
+          outputStreamWriter.write('\n');
+        } catch (IOException e) {
+          throw new UncheckedIOException(e);
+        }
+      }
+    }
+
+    void close() {
+      if (outputStreamWriter != null) {
+        try {
+          outputStreamWriter.close();
+        } catch (IOException e) {
+          throw new UncheckedIOException(e);
+        }
+      }
+    }
+  }
+}
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 f4e27a1..a8058ac 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -234,7 +234,12 @@
       Set<DexType> lockCandidates,
       Map<DexType, Visibility> initClassReferences,
       Set<DexMethod> recordFieldValuesReferences) {
-    super(committedItems, classToFeatureSplitMap, mainDexInfo, missingClasses, startupOrder);
+    super(
+        committedItems,
+        classToFeatureSplitMap,
+        mainDexInfo,
+        missingClasses,
+        startupOrder);
     this.deadProtoTypes = deadProtoTypes;
     this.liveTypes = liveTypes;
     this.targetedMethods = targetedMethods;
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
index 19bbe92..813328d 100644
--- a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
@@ -87,6 +87,8 @@
                       .createField(clazz.type, clinitField.type, clinitField.name))
               .setAccessFlags(accessFlags)
               .setApiLevel(appView.computedMinApiLevel())
+              .disableAndroidApiLevelCheckIf(
+                  !appView.options().apiModelingOptions().isApiCallerIdentificationEnabled())
               .build();
       clazz.appendStaticField(encodedClinitField);
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
new file mode 100644
index 0000000..fcd894d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
@@ -0,0 +1,161 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
+import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.dex.code.CfOrDexInstruction;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
+import java.util.ListIterator;
+
+public class ComputeApiLevelUseRegistry extends UseRegistry<ProgramMethod> {
+
+  protected final AppView<?> appView;
+  private final AndroidApiLevelCompute apiLevelCompute;
+  private final boolean isEnabled;
+  private ComputedApiLevel maxApiReferenceLevel;
+
+  public ComputeApiLevelUseRegistry(
+      AppView<?> appView, ProgramMethod context, AndroidApiLevelCompute apiLevelCompute) {
+    super(appView, context);
+    this.appView = appView;
+    this.apiLevelCompute = apiLevelCompute;
+    isEnabled = apiLevelCompute.isEnabled();
+    maxApiReferenceLevel = appView.computedMinApiLevel();
+  }
+
+  @Override
+  public void registerInitClass(DexType clazz) {
+    // Intentionally empty since init class as synthesized.
+  }
+
+  @Override
+  public void registerInvokeVirtual(DexMethod invokedMethod) {
+    setMaxApiReferenceLevel(invokedMethod);
+  }
+
+  @Override
+  public void registerInvokeDirect(DexMethod invokedMethod) {
+    setMaxApiReferenceLevel(invokedMethod);
+  }
+
+  @Override
+  public void registerInvokeStatic(DexMethod invokedMethod) {
+    setMaxApiReferenceLevel(invokedMethod);
+  }
+
+  @Override
+  public void registerInvokeInterface(DexMethod invokedMethod) {
+    setMaxApiReferenceLevel(invokedMethod);
+  }
+
+  @Override
+  public void registerInvokeSuper(DexMethod invokedMethod) {
+    setMaxApiReferenceLevel(invokedMethod);
+  }
+
+  @Override
+  public void registerInstanceFieldRead(DexField field) {
+    setMaxApiReferenceLevel(field);
+  }
+
+  @Override
+  public void registerInstanceFieldReadFromMethodHandle(DexField field) {
+    setMaxApiReferenceLevel(field);
+  }
+
+  @Override
+  public void registerInstanceFieldWrite(DexField field) {
+    setMaxApiReferenceLevel(field);
+  }
+
+  @Override
+  public void registerInstanceFieldWriteFromMethodHandle(DexField field) {
+    setMaxApiReferenceLevel(field);
+  }
+
+  @Override
+  public void registerNewInstance(DexType type) {
+    setMaxApiReferenceLevel(type);
+  }
+
+  @Override
+  public void registerStaticFieldRead(DexField field) {
+    setMaxApiReferenceLevel(field);
+  }
+
+  @Override
+  public void registerStaticFieldReadFromMethodHandle(DexField field) {
+    setMaxApiReferenceLevel(field);
+  }
+
+  @Override
+  public void registerStaticFieldWrite(DexField field) {
+    setMaxApiReferenceLevel(field);
+  }
+
+  @Override
+  public void registerStaticFieldWriteFromMethodHandle(DexField field) {
+    setMaxApiReferenceLevel(field);
+  }
+
+  @Override
+  public void registerConstClass(
+      DexType type,
+      ListIterator<? extends CfOrDexInstruction> iterator,
+      boolean ignoreCompatRules) {
+    setMaxApiReferenceLevel(type);
+  }
+
+  @Override
+  public void registerCheckCast(DexType type, boolean ignoreCompatRules) {
+    // CheckCast are not causing soft verification issues
+  }
+
+  @Override
+  public void registerSafeCheckCast(DexType type) {
+    // CheckCast are not causing soft verification issues
+  }
+
+  @Override
+  public void registerTypeReference(DexType type) {
+    // Type references are OK as long as we do not have a use on them
+  }
+
+  @Override
+  public void registerInstanceOf(DexType type) {
+    // InstanceOf are not causing soft verification issues
+  }
+
+  @Override
+  public void registerExceptionGuard(DexType guard) {
+    setMaxApiReferenceLevel(guard);
+  }
+
+  @Override
+  public void registerMethodHandle(DexMethodHandle methodHandle, MethodHandleUse use) {
+    super.registerMethodHandle(methodHandle, use);
+  }
+
+  private void setMaxApiReferenceLevel(DexReference reference) {
+    if (isEnabled) {
+      maxApiReferenceLevel =
+          maxApiReferenceLevel.max(
+              apiLevelCompute.computeApiLevelForLibraryReference(
+                  reference, apiLevelCompute.getPlatformApiLevelOrUnknown(appView)));
+    }
+  }
+
+  public ComputedApiLevel getMaxApiReferenceLevel() {
+    return maxApiReferenceLevel;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index 192b16a..f7ffc42 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.isInvokeDynamicOnRecord;
 
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
-import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.dex.code.CfOrDexInstruction;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
@@ -18,29 +17,23 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
 import java.util.ListIterator;
 
-public class DefaultEnqueuerUseRegistry extends UseRegistry<ProgramMethod> {
+public class DefaultEnqueuerUseRegistry extends ComputeApiLevelUseRegistry {
 
   protected final AppView<? extends AppInfoWithClassHierarchy> appView;
   protected final Enqueuer enqueuer;
-  private final AndroidApiLevelCompute apiLevelCompute;
-  private ComputedApiLevel maxApiReferenceLevel;
 
   public DefaultEnqueuerUseRegistry(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       ProgramMethod context,
       Enqueuer enqueuer,
       AndroidApiLevelCompute apiLevelCompute) {
-    super(appView, context);
+    super(appView, context, apiLevelCompute);
     this.appView = appView;
     this.enqueuer = enqueuer;
-    this.apiLevelCompute = apiLevelCompute;
-    maxApiReferenceLevel = appView.computedMinApiLevel();
   }
 
   public DexProgramClass getContextHolder() {
@@ -53,6 +46,7 @@
 
   @Override
   public void registerInitClass(DexType clazz) {
+    super.registerInitClass(clazz);
     enqueuer.traceInitClass(clazz, getContext());
   }
 
@@ -64,90 +58,90 @@
 
   @Override
   public void registerInvokeVirtual(DexMethod invokedMethod) {
-    setMaxApiReferenceLevel(invokedMethod);
+    super.registerInvokeVirtual(invokedMethod);
     enqueuer.traceInvokeVirtual(invokedMethod, getContext());
   }
 
   @Override
   public void registerInvokeDirect(DexMethod invokedMethod) {
-    setMaxApiReferenceLevel(invokedMethod);
+    super.registerInvokeDirect(invokedMethod);
     enqueuer.traceInvokeDirect(invokedMethod, getContext());
   }
 
   @Override
   public void registerInvokeStatic(DexMethod invokedMethod) {
-    setMaxApiReferenceLevel(invokedMethod);
+    super.registerInvokeStatic(invokedMethod);
     enqueuer.traceInvokeStatic(invokedMethod, getContext());
   }
 
   @Override
   public void registerInvokeInterface(DexMethod invokedMethod) {
-    setMaxApiReferenceLevel(invokedMethod);
+    super.registerInvokeInterface(invokedMethod);
     enqueuer.traceInvokeInterface(invokedMethod, getContext());
   }
 
   @Override
   public void registerInvokeSuper(DexMethod invokedMethod) {
-    setMaxApiReferenceLevel(invokedMethod);
+    super.registerInvokeSuper(invokedMethod);
     enqueuer.traceInvokeSuper(invokedMethod, getContext());
   }
 
   @Override
   public void registerInstanceFieldRead(DexField field) {
-    setMaxApiReferenceLevel(field);
+    super.registerInstanceFieldRead(field);
     enqueuer.traceInstanceFieldRead(field, getContext());
   }
 
   @Override
   public void registerInstanceFieldReadFromMethodHandle(DexField field) {
-    setMaxApiReferenceLevel(field);
+    super.registerInstanceFieldReadFromMethodHandle(field);
     enqueuer.traceInstanceFieldReadFromMethodHandle(field, getContext());
   }
 
   private void registerInstanceFieldReadFromRecordMethodHandle(DexField field) {
-    setMaxApiReferenceLevel(field);
+    super.registerInstanceFieldWriteFromMethodHandle(field);
     enqueuer.traceInstanceFieldReadFromRecordMethodHandle(field, getContext());
   }
 
   @Override
   public void registerInstanceFieldWrite(DexField field) {
-    setMaxApiReferenceLevel(field);
+    super.registerInstanceFieldWrite(field);
     enqueuer.traceInstanceFieldWrite(field, getContext());
   }
 
   @Override
   public void registerInstanceFieldWriteFromMethodHandle(DexField field) {
-    setMaxApiReferenceLevel(field);
+    super.registerInstanceFieldWriteFromMethodHandle(field);
     enqueuer.traceInstanceFieldWriteFromMethodHandle(field, getContext());
   }
 
   @Override
   public void registerNewInstance(DexType type) {
-    setMaxApiReferenceLevel(type);
+    super.registerNewInstance(type);
     enqueuer.traceNewInstance(type, getContext());
   }
 
   @Override
   public void registerStaticFieldRead(DexField field) {
-    setMaxApiReferenceLevel(field);
+    super.registerStaticFieldRead(field);
     enqueuer.traceStaticFieldRead(field, getContext());
   }
 
   @Override
   public void registerStaticFieldReadFromMethodHandle(DexField field) {
-    setMaxApiReferenceLevel(field);
+    super.registerStaticFieldReadFromMethodHandle(field);
     enqueuer.traceStaticFieldReadFromMethodHandle(field, getContext());
   }
 
   @Override
   public void registerStaticFieldWrite(DexField field) {
-    setMaxApiReferenceLevel(field);
+    super.registerStaticFieldWrite(field);
     enqueuer.traceStaticFieldWrite(field, getContext());
   }
 
   @Override
   public void registerStaticFieldWriteFromMethodHandle(DexField field) {
-    setMaxApiReferenceLevel(field);
+    super.registerStaticFieldWriteFromMethodHandle(field);
     enqueuer.traceStaticFieldWriteFromMethodHandle(field, getContext());
   }
 
@@ -156,32 +150,37 @@
       DexType type,
       ListIterator<? extends CfOrDexInstruction> iterator,
       boolean ignoreCompatRules) {
+    super.registerConstClass(type, iterator, ignoreCompatRules);
     enqueuer.traceConstClass(type, getContext(), iterator, ignoreCompatRules);
   }
 
   @Override
   public void registerCheckCast(DexType type, boolean ignoreCompatRules) {
+    super.registerCheckCast(type, ignoreCompatRules);
     enqueuer.traceCheckCast(type, getContext(), ignoreCompatRules);
   }
 
   @Override
   public void registerSafeCheckCast(DexType type) {
+    super.registerSafeCheckCast(type);
     enqueuer.traceSafeCheckCast(type, getContext());
   }
 
   @Override
   public void registerTypeReference(DexType type) {
+    super.registerTypeReference(type);
     enqueuer.traceTypeReference(type, getContext());
   }
 
   @Override
   public void registerInstanceOf(DexType type) {
+    super.registerInstanceOf(type);
     enqueuer.traceInstanceOf(type, getContext());
   }
 
   @Override
   public void registerExceptionGuard(DexType guard) {
-    setMaxApiReferenceLevel(guard);
+    super.registerExceptionGuard(guard);
     enqueuer.traceExceptionGuard(guard, getContext());
   }
 
@@ -219,15 +218,4 @@
       }
     }
   }
-
-  private void setMaxApiReferenceLevel(DexReference reference) {
-    maxApiReferenceLevel =
-        maxApiReferenceLevel.max(
-            apiLevelCompute.computeApiLevelForLibraryReference(
-                reference, apiLevelCompute.getPlatformApiLevelOrUnknown(appView)));
-  }
-
-  public ComputedApiLevel getMaxApiReferenceLevel() {
-    return maxApiReferenceLevel;
-  }
 }
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 90b9dfb..74202d3 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2508,39 +2508,41 @@
 
   private void handleInvokeOfDirectTarget(
       DexMethod reference, ProgramDefinition context, KeepReason reason) {
-    DexType holder = reference.holder;
-    DexProgramClass clazz = getProgramClassOrNull(holder, context);
-    if (clazz == null) {
-      recordMethodReference(reference, context);
-      return;
-    }
-    // TODO(zerny): Is it ok that we lookup in both the direct and virtual pool here?
-    DexEncodedMethod encodedMethod = clazz.lookupMethod(reference);
-    if (encodedMethod == null) {
-      failedMethodResolutionTargets.add(reference);
-      return;
-    }
+    resolveMethod(reference, context, reason)
+        .forEachMethodResolutionResult(
+            resolutionResult -> {
+              if (resolutionResult.isFailedResolution()) {
+                failedMethodResolutionTargets.add(reference);
+                return;
+              }
 
-    ProgramMethod method = new ProgramMethod(clazz, encodedMethod);
+              if (!resolutionResult.isSingleResolution()
+                  || !resolutionResult.getResolvedHolder().isProgramClass()) {
+                return;
+              }
 
-    // We have to mark the resolved method as targeted even if it cannot actually be invoked
-    // to make sure the invocation will keep failing in the appropriate way.
-    markMethodAsTargeted(method, reason);
+              ProgramMethod resolvedMethod =
+                  resolutionResult.asSingleResolution().getResolvedProgramMethod();
 
-    // Only mark methods for which invocation will succeed at runtime live.
-    if (encodedMethod.isStatic()) {
-      return;
-    }
+              // We have to mark the resolved method as targeted even if it cannot actually be
+              // invoked to make sure the invocation will keep failing in the appropriate way.
+              markMethodAsTargeted(resolvedMethod, reason);
 
-    markDirectStaticOrConstructorMethodAsLive(method, reason);
+              // Only mark methods for which invocation will succeed at runtime live.
+              if (resolvedMethod.getAccessFlags().isStatic()) {
+                return;
+              }
 
-    // It is valid to have an invoke-direct instruction in a default interface method that
-    // targets another default method in the same interface (see testInvokeSpecialToDefault-
-    // Method). In a class, that would lead to a verification error.
-    if (encodedMethod.isNonPrivateVirtualMethod()
-        && virtualMethodsTargetedByInvokeDirect.add(encodedMethod.getReference())) {
-      workList.enqueueMarkMethodLiveAction(method, context, reason);
-    }
+              markDirectStaticOrConstructorMethodAsLive(resolvedMethod, reason);
+
+              // It is valid to have an invoke-direct instruction in a default interface method that
+              // targets another default method in the same interface. In a class, that would lead
+              // to a verification error. See also testInvokeSpecialToDefaultMethod.
+              if (resolvedMethod.getDefinition().isNonPrivateVirtualMethod()
+                  && virtualMethodsTargetedByInvokeDirect.add(resolvedMethod.getReference())) {
+                workList.enqueueMarkMethodLiveAction(resolvedMethod, context, reason);
+              }
+            });
   }
 
   private void ensureFromLibraryOrThrow(DexType type, DexLibraryClass context) {
@@ -3575,7 +3577,9 @@
         && appView.options().getProguardConfiguration().getKeepAttributes().signature) {
       registerAnalysis(new GenericSignatureEnqueuerAnalysis(enqueuerDefinitionSupplier));
     }
-    registerAnalysis(new ApiModelAnalysis(appView));
+    if (options.apiModelingOptions().enableLibraryApiModeling) {
+      registerAnalysis(new ApiModelAnalysis(appView));
+    }
 
     // Transfer the minimum keep info from the root set into the Enqueuer state.
     includeMinimumKeepInfo(rootSet);
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
index 0173fb2..41ec85a 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
@@ -29,6 +29,7 @@
   private final boolean allowConstantArgumentOptimization;
   private final boolean allowInlining;
   private final boolean allowMethodStaticizing;
+  private final boolean allowParameterRemoval;
   private final boolean allowParameterReordering;
   private final boolean allowParameterTypeStrengthening;
   private final boolean allowReturnTypeStrengthening;
@@ -42,6 +43,7 @@
     this.allowConstantArgumentOptimization = builder.isConstantArgumentOptimizationAllowed();
     this.allowInlining = builder.isInliningAllowed();
     this.allowMethodStaticizing = builder.isMethodStaticizingAllowed();
+    this.allowParameterRemoval = builder.isParameterRemovalAllowed();
     this.allowParameterReordering = builder.isParameterReorderingAllowed();
     this.allowParameterTypeStrengthening = builder.isParameterTypeStrengtheningAllowed();
     this.allowReturnTypeStrengthening = builder.isReturnTypeStrengtheningAllowed();
@@ -60,13 +62,6 @@
     return isParameterRemovalAllowed(configuration);
   }
 
-  public boolean isParameterRemovalAllowed(GlobalKeepInfoConfiguration configuration) {
-    return isClosedWorldReasoningAllowed(configuration)
-        && isOptimizationAllowed(configuration)
-        && isShrinkingAllowed(configuration)
-        && !isCheckDiscardedEnabled(configuration);
-  }
-
   public boolean isClassInliningAllowed(GlobalKeepInfoConfiguration configuration) {
     return isOptimizationAllowed(configuration) && internalIsClassInliningAllowed();
   }
@@ -111,6 +106,18 @@
     return allowMethodStaticizing;
   }
 
+  public boolean isParameterRemovalAllowed(GlobalKeepInfoConfiguration configuration) {
+    return isClosedWorldReasoningAllowed(configuration)
+        && isOptimizationAllowed(configuration)
+        && isShrinkingAllowed(configuration)
+        && !isCheckDiscardedEnabled(configuration)
+        && internalIsParameterRemovalAllowed();
+  }
+
+  boolean internalIsParameterRemovalAllowed() {
+    return allowParameterRemoval;
+  }
+
   public boolean isParameterReorderingAllowed(GlobalKeepInfoConfiguration configuration) {
     return isClosedWorldReasoningAllowed(configuration)
         && isOptimizationAllowed(configuration)
@@ -188,6 +195,7 @@
     private boolean allowConstantArgumentOptimization;
     private boolean allowInlining;
     private boolean allowMethodStaticizing;
+    private boolean allowParameterRemoval;
     private boolean allowParameterReordering;
     private boolean allowParameterTypeStrengthening;
     private boolean allowReturnTypeStrengthening;
@@ -205,6 +213,7 @@
       allowConstantArgumentOptimization = original.internalIsConstantArgumentOptimizationAllowed();
       allowInlining = original.internalIsInliningAllowed();
       allowMethodStaticizing = original.internalIsMethodStaticizingAllowed();
+      allowParameterRemoval = original.internalIsParameterRemovalAllowed();
       allowParameterReordering = original.internalIsParameterReorderingAllowed();
       allowParameterTypeStrengthening = original.internalIsParameterTypeStrengtheningAllowed();
       allowReturnTypeStrengthening = original.internalIsReturnTypeStrengtheningAllowed();
@@ -308,6 +317,25 @@
       return setAllowMethodStaticizing(false);
     }
 
+    // Parameter removal.
+
+    public boolean isParameterRemovalAllowed() {
+      return allowParameterRemoval;
+    }
+
+    public Builder setAllowParameterRemoval(boolean allowParameterRemoval) {
+      this.allowParameterRemoval = allowParameterRemoval;
+      return self();
+    }
+
+    public Builder allowParameterRemoval() {
+      return setAllowParameterRemoval(true);
+    }
+
+    public Builder disallowParameterRemoval() {
+      return setAllowParameterRemoval(false);
+    }
+
     // Parameter reordering.
 
     public boolean isParameterReorderingAllowed() {
@@ -433,6 +461,7 @@
               == other.internalIsConstantArgumentOptimizationAllowed()
           && isInliningAllowed() == other.internalIsInliningAllowed()
           && isMethodStaticizingAllowed() == other.internalIsMethodStaticizingAllowed()
+          && isParameterRemovalAllowed() == other.internalIsParameterRemovalAllowed()
           && isParameterReorderingAllowed() == other.internalIsParameterReorderingAllowed()
           && isParameterTypeStrengtheningAllowed()
               == other.internalIsParameterTypeStrengtheningAllowed()
@@ -456,6 +485,7 @@
           .disallowConstantArgumentOptimization()
           .disallowInlining()
           .disallowMethodStaticizing()
+          .disallowParameterRemoval()
           .disallowParameterReordering()
           .disallowParameterTypeStrengthening()
           .disallowReturnTypeStrengthening()
@@ -471,6 +501,7 @@
           .allowConstantArgumentOptimization()
           .allowInlining()
           .allowMethodStaticizing()
+          .allowParameterRemoval()
           .allowParameterReordering()
           .allowParameterTypeStrengthening()
           .allowReturnTypeStrengthening()
@@ -510,6 +541,11 @@
       return self();
     }
 
+    public Joiner disallowParameterRemoval() {
+      builder.disallowParameterRemoval();
+      return self();
+    }
+
     public Joiner disallowParameterReordering() {
       builder.disallowParameterReordering();
       return self();
@@ -552,6 +588,7 @@
               Joiner::disallowConstantArgumentOptimization)
           .applyIf(!joiner.builder.isInliningAllowed(), Joiner::disallowInlining)
           .applyIf(!joiner.builder.isMethodStaticizingAllowed(), Joiner::disallowMethodStaticizing)
+          .applyIf(!joiner.builder.isParameterRemovalAllowed(), Joiner::disallowParameterRemoval)
           .applyIf(
               !joiner.builder.isParameterReorderingAllowed(), Joiner::disallowParameterReordering)
           .applyIf(
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index 7275e72..fddac9e 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -1706,7 +1706,8 @@
     }
 
     private boolean isInterfaceMethodNeedingDesugaring(ProgramDefinition item) {
-      return options.isInterfaceMethodDesugaringEnabled()
+      return !isMainDexRootSetBuilder()
+          && options.isInterfaceMethodDesugaringEnabled()
           && item.isMethod()
           && item.asMethod().getHolder().isInterface()
           && !item.asMethod().getDefinition().isClassInitializer()
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 8760fb8..bc1ce29 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -533,7 +533,7 @@
     }
     // Only merge if api reference level of source class is equal to target class. The check is
     // somewhat expensive.
-    if (appView.options().apiModelingOptions().enableApiCallerIdentification) {
+    if (appView.options().apiModelingOptions().isApiCallerIdentificationEnabled()) {
       ComputedApiLevel sourceApiLevel =
           getApiReferenceLevelForMerging(appView, apiLevelCompute, sourceClass);
       ComputedApiLevel targetApiLevel =
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
index 2c3b88b..06c4094 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
@@ -36,7 +36,7 @@
       ProgramMethod inlinee,
       InternalOptions options,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
-    if (!options.apiModelingOptions().enableApiCallerIdentification) {
+    if (!options.apiModelingOptions().isApiCallerIdentificationEnabled()) {
       return true;
     }
     if (caller.getHolderType() == inlinee.getHolderType()) {
@@ -90,13 +90,19 @@
       DexMethod original,
       AndroidApiLevelCompute androidApiLevelCompute,
       InternalOptions options) {
+    // If we are not using the api database and we have the platform build, then we assume we are
+    // running with boot class path as min api and all definitions are accessible at runtime.
+    if (!androidApiLevelCompute.isEnabled()) {
+      assert !options.apiModelingOptions().enableLibraryApiModeling;
+      return options.isAndroidPlatformBuild();
+    }
+    assert options.apiModelingOptions().enableLibraryApiModeling;
     ComputedApiLevel apiLevel =
         androidApiLevelCompute.computeApiLevelForLibraryReference(
             method.getReference(), ComputedApiLevel.unknown());
     if (apiLevel.isUnknownApiLevel()) {
       return false;
     }
-    assert options.apiModelingOptions().enableApiCallerIdentification;
     ComputedApiLevel apiLevelOfOriginal =
         androidApiLevelCompute.computeApiLevelForLibraryReference(
             original, ComputedApiLevel.unknown());
@@ -107,15 +113,21 @@
   }
 
   public static boolean isApiSafeForReference(LibraryDefinition definition, AppView<?> appView) {
-    return isApiSafeForReference(definition, appView.apiLevelCompute(), appView.options());
+    if (appView.options().isAndroidPlatformBuild()) {
+      assert definition != null;
+      return true;
+    }
+    return isApiSafeForReference(
+        definition, appView.apiLevelCompute(), appView.options(), appView.dexItemFactory());
   }
 
   private static boolean isApiSafeForReference(
       LibraryDefinition definition,
       AndroidApiLevelCompute androidApiLevelCompute,
-      InternalOptions options) {
+      InternalOptions options,
+      DexItemFactory factory) {
     if (!options.apiModelingOptions().enableApiCallerIdentification) {
-      return false;
+      return factory.libraryTypesAssumedToBePresent.contains(definition.getContextType());
     }
     ComputedApiLevel apiLevel =
         androidApiLevelCompute.computeApiLevelForLibraryReference(
@@ -163,7 +175,7 @@
       // Program and classpath classes are not api level dependent.
       return true;
     }
-    if (!appView.options().apiModelingOptions().enableApiCallerIdentification) {
+    if (!appView.options().apiModelingOptions().isApiCallerIdentificationEnabled()) {
       // Conservatively bail out if we don't have api modeling.
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
index b30c961..919d998 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -249,13 +249,20 @@
     }
   }
 
-  private static Collection<?> createStartupProfileProviders(List<Path> startupProfileFiles) {
+  private static Collection<Object> createStartupProfileProviders(List<Path> startupProfileFiles) {
     List<Object> startupProfileProviders = new ArrayList<>();
     for (Path startupProfileFile : startupProfileFiles) {
-      callReflectiveUtilsMethod(
-          "createStartupProfileProviderFromDumpFile",
-          new Class<?>[] {Path.class},
-          fn -> startupProfileProviders.add(fn.apply(new Object[] {startupProfileFile})));
+      boolean found =
+          callReflectiveUtilsMethod(
+              "createStartupProfileProviderFromDumpFile",
+              new Class<?>[] {Path.class},
+              fn -> startupProfileProviders.add(fn.apply(new Object[] {startupProfileFile})));
+      if (!found) {
+        System.out.println(
+            "Unable to add startup profiles as input. "
+                + "Method createStartupProfileProviderFromDumpFile() was not found.");
+        break;
+      }
     }
     return startupProfileProviders;
   }
@@ -278,20 +285,20 @@
     }
   }
 
-  private static void callReflectiveUtilsMethod(
+  private static boolean callReflectiveUtilsMethod(
       String methodName, Class<?>[] parameters, Consumer<Function<Object[], Object>> fnConsumer) {
     Class<?> utilsClass;
     try {
       utilsClass = Class.forName("com.android.tools.r8.utils.CompileDumpUtils");
     } catch (ClassNotFoundException e) {
-      return;
+      return false;
     }
 
     Method declaredMethod;
     try {
       declaredMethod = utilsClass.getMethod(methodName, parameters);
     } catch (NoSuchMethodException e) {
-      return;
+      return false;
     }
 
     fnConsumer.accept(
@@ -302,6 +309,7 @@
             throw new RuntimeException(e);
           }
         });
+    return true;
   }
 
   // We cannot use StringResource since this class is added to the class path and has access only
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpD8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpD8.java
index 32892d7..613701c 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpD8.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpD8.java
@@ -20,7 +20,7 @@
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.function.Consumer;
-import java.util.stream.Collectors;
+import java.util.function.Function;
 
 /**
  * Wrapper to make it easy to call D8 mode when compiling a dump file.
@@ -161,12 +161,7 @@
     getReflectiveBuilderMethod(commandBuilder, "setAndroidPlatformBuild", boolean.class)
         .accept(new Object[] {androidPlatformBuild});
     getReflectiveBuilderMethod(commandBuilder, "addStartupProfileProviders", Collection.class)
-        .accept(
-            new Object[] {
-              startupProfileFiles.stream()
-                  .map(CompileDumpUtils::createStartupProfileProviderFromDumpFile)
-                  .collect(Collectors.toList())
-            });
+        .accept(new Object[] {createStartupProfileProviders(startupProfileFiles)});
     if (desugaredLibJson != null) {
       commandBuilder.addDesugaredLibraryConfiguration(readAllBytesJava7(desugaredLibJson));
     }
@@ -184,6 +179,24 @@
     }
   }
 
+  private static Collection<Object> createStartupProfileProviders(List<Path> startupProfileFiles) {
+    List<Object> startupProfileProviders = new ArrayList<>();
+    for (Path startupProfileFile : startupProfileFiles) {
+      boolean found =
+          callReflectiveUtilsMethod(
+              "createStartupProfileProviderFromDumpFile",
+              new Class<?>[] {Path.class},
+              fn -> startupProfileProviders.add(fn.apply(new Object[] {startupProfileFile})));
+      if (!found) {
+        System.out.println(
+            "Unable to add startup profiles as input. "
+                + "Method createStartupProfileProviderFromDumpFile() was not found.");
+        break;
+      }
+    }
+    return startupProfileProviders;
+  }
+
   private static Consumer<Object[]> getReflectiveBuilderMethod(
       D8Command.Builder builder, String setter, Class<?>... parameters) {
     try {
@@ -202,6 +215,33 @@
     }
   }
 
+  private static boolean callReflectiveUtilsMethod(
+      String methodName, Class<?>[] parameters, Consumer<Function<Object[], Object>> fnConsumer) {
+    Class<?> utilsClass;
+    try {
+      utilsClass = Class.forName("com.android.tools.r8.utils.CompileDumpUtils");
+    } catch (ClassNotFoundException e) {
+      return false;
+    }
+
+    Method declaredMethod;
+    try {
+      declaredMethod = utilsClass.getMethod(methodName, parameters);
+    } catch (NoSuchMethodException e) {
+      return false;
+    }
+
+    fnConsumer.accept(
+        args -> {
+          try {
+            return declaredMethod.invoke(null, args);
+          } catch (Exception e) {
+            throw new RuntimeException(e);
+          }
+        });
+    return true;
+  }
+
   // We cannot use StringResource since this class is added to the class path and has access only
   // to the public APIs.
   private static String readAllBytesJava7(Path filePath) {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 07322dc..82d7e77 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -59,6 +59,7 @@
 import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
 import com.android.tools.r8.horizontalclassmerging.Policy;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
+import com.android.tools.r8.ir.analysis.proto.ProtoReferences;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.desugar.TypeRewriter;
@@ -72,6 +73,7 @@
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorEventConsumer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
+import com.android.tools.r8.profile.art.ArtProfileOptions;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
@@ -89,6 +91,7 @@
 import com.android.tools.r8.utils.structural.Ordered;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
@@ -238,7 +241,6 @@
   }
 
   void enableProtoShrinking() {
-    inlinerOptions.applyInliningToInlinee = true;
     enableFieldBitAccessAnalysis = true;
     protoShrinking.enableGeneratedMessageLiteShrinking = true;
     protoShrinking.enableGeneratedMessageLiteBuilderShrinking = true;
@@ -275,10 +277,14 @@
     androidPlatformBuild = isAndroidPlatformBuild;
     // Configure options according to platform build assumptions.
     // See go/r8platformflag and b/232073181.
-    apiModelingOptions().disableMissingApiModeling();
+    apiModelingOptions().disableApiModeling();
     enableBackportMethods = false;
   }
 
+  public boolean isAndroidPlatformBuild() {
+    return androidPlatformBuild;
+  }
+
   public boolean printTimes = System.getProperty("com.android.tools.r8.printtimes") != null;
   // To print memory one also have to enable printtimes.
   public boolean printMemory = System.getProperty("com.android.tools.r8.printmemory") != null;
@@ -342,7 +348,6 @@
   public boolean readDebugSetFileEvent = false;
   public boolean disableL8AnnotationRemoval =
       System.getProperty("com.android.tools.r8.disableL8AnnotationRemoval") != null;
-  public boolean enableVisibilityBridgeRemoval = true;
 
   public int callGraphLikelySpuriousCallEdgeThreshold = 50;
 
@@ -815,7 +820,8 @@
       new KotlinOptimizationOptions();
   private final ApiModelTestingOptions apiModelTestingOptions = new ApiModelTestingOptions();
   private final DesugarSpecificOptions desugarSpecificOptions = new DesugarSpecificOptions();
-  private final StartupOptions startupOptions = new StartupOptions(this);
+  private final ArtProfileOptions artProfileOptions = new ArtProfileOptions();
+  private final StartupOptions startupOptions = new StartupOptions();
   private final StartupInstrumentationOptions startupInstrumentationOptions =
       new StartupInstrumentationOptions();
   public final TestingOptions testing = new TestingOptions();
@@ -877,6 +883,10 @@
     return openClosedInterfacesOptions;
   }
 
+  public ArtProfileOptions getArtProfileOptions() {
+    return artProfileOptions;
+  }
+
   public StartupOptions getStartupOptions() {
     return startupOptions;
   }
@@ -1450,6 +1460,11 @@
     }
   }
 
+  public interface ApplyInliningToInlineePredicate {
+
+    boolean test(AppView<?> appView, ProgramMethod method, int inliningDepth);
+  }
+
   public class InlinerOptions {
 
     public boolean enableInlining =
@@ -1475,15 +1490,12 @@
     // GMS Core.
     public int inliningControlFlowResolutionBlocksThreshold = 15;
 
-    // TODO(b/141451716): Evaluate the effect of allowing inlining in the inlinee.
-    public boolean applyInliningToInlinee =
-        System.getProperty("com.android.tools.r8.applyInliningToInlinee") != null;
-    public int applyInliningToInlineeMaxDepth = 0;
-
     public boolean enableInliningOfInvokesWithClassInitializationSideEffects = true;
     public boolean enableInliningOfInvokesWithNullableReceivers = true;
     public boolean disableInliningOfLibraryMethodOverrides = true;
 
+    public ApplyInliningToInlineePredicate applyInliningToInlineePredicateForTesting = null;
+
     public int getSimpleInliningInstructionLimit() {
       // If a custom simple inlining instruction limit is set, then use that.
       if (simpleInliningInstructionLimit >= 0) {
@@ -1497,6 +1509,17 @@
       assert isGeneratingDex();
       return 5;
     }
+
+    public boolean shouldApplyInliningToInlinee(
+        AppView<?> appView, ProgramMethod inlinee, int inliningDepth) {
+      if (applyInliningToInlineePredicateForTesting != null) {
+        return applyInliningToInlineePredicateForTesting.test(appView, inlinee, inliningDepth);
+      }
+      if (protoShrinking.shouldApplyInliningToInlinee(appView, inlinee, inliningDepth)) {
+        return true;
+      }
+      return false;
+    }
   }
 
   public class HorizontalClassMergerOptions {
@@ -1699,6 +1722,14 @@
 
   public static class ApiModelTestingOptions {
 
+    // Flag to specify if we should load the database or not. The api database is used for
+    // library member rebinding.
+    public boolean enableLibraryApiModeling =
+        System.getProperty("com.android.tools.r8.disableApiModeling") == null;
+
+    // The flag enableApiCallerIdentification controls if we can inline or merge targets with
+    // different api levels. It is also the flag that specifies if we assign api levels to
+    // references.
     public boolean enableApiCallerIdentification =
         System.getProperty("com.android.tools.r8.disableApiModeling") == null;
     public boolean checkAllApiReferencesAreSet =
@@ -1736,11 +1767,31 @@
           });
     }
 
+    public boolean isApiLibraryModelingEnabled() {
+      return enableLibraryApiModeling;
+    }
+
+    public boolean isCheckAllApiReferencesAreSet() {
+      return enableLibraryApiModeling && checkAllApiReferencesAreSet;
+    }
+
+    public boolean isApiCallerIdentificationEnabled() {
+      return enableLibraryApiModeling && enableApiCallerIdentification;
+    }
+
+    public void disableApiModeling() {
+      enableLibraryApiModeling = false;
+      enableApiCallerIdentification = false;
+      enableOutliningOfMethods = false;
+      enableStubbingOfClasses = false;
+      checkAllApiReferencesAreSet = false;
+    }
+
     /**
      * Disable the workarounds for missing APIs. This does not disable the use of the database, just
      * the introduction of soft-verification workarounds for potentially missing API references.
      */
-    public void disableMissingApiModeling() {
+    public void disableOutliningAndStubbing() {
       enableOutliningOfMethods = false;
       enableStubbingOfClasses = false;
     }
@@ -1749,7 +1800,7 @@
       enableApiCallerIdentification = false;
     }
 
-    public void disableSubbingOfClasses() {
+    public void disableStubbingOfClasses() {
       enableStubbingOfClasses = false;
     }
   }
@@ -1787,6 +1838,15 @@
     public boolean isEnumLiteProtoShrinkingEnabled() {
       return enableEnumLiteProtoShrinking;
     }
+
+    public boolean shouldApplyInliningToInlinee(
+        AppView<?> appView, ProgramMethod inlinee, int inliningDepth) {
+      if (isProtoShrinkingEnabled() && inliningDepth == 1) {
+        ProtoReferences protoReferences = appView.protoShrinker().getProtoReferences();
+        return inlinee.getHolderType() == protoReferences.generatedMessageLiteType;
+      }
+      return false;
+    }
   }
 
   public static class TestingOptions {
@@ -1836,6 +1896,8 @@
     public ArgumentPropagatorEventConsumer argumentPropagatorEventConsumer =
         ArgumentPropagatorEventConsumer.emptyConsumer();
 
+    public Predicate<DexProgramClass> isEligibleForBridgeHoisting = Predicates.alwaysTrue();
+
     // Force writing the specified bytes as the DEX version content.
     public byte[] forceDexVersionBytes = null;
 
@@ -1910,6 +1972,7 @@
     public boolean enableDeadSwitchCaseElimination = true;
     public boolean enableInvokeSuperToInvokeVirtualRewriting = true;
     public boolean enableMultiANewArrayDesugaringForClassFiles = false;
+    public boolean enableRedundantConstructorBridgeRemoval = false;
     public boolean enableSwitchToIfRewriting = true;
     public boolean enableEnumUnboxingDebugLogs =
         System.getProperty("com.android.tools.r8.enableEnumUnboxingDebugLogs") != null;
@@ -2692,4 +2755,8 @@
   public boolean canHaveDalvikEmptyAnnotationSetBug() {
     return canHaveBugPresentUntil(AndroidApiLevel.J_MR1);
   }
+
+  public boolean canHaveNonReboundConstructorInvoke() {
+    return isGeneratingDex() && minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.L);
+  }
 }
diff --git a/src/test/debugTestResources/ClassInitializerEmpty.java b/src/test/debugTestResources/ClassInitializerEmpty.java
deleted file mode 100644
index 9062d62..0000000
--- a/src/test/debugTestResources/ClassInitializerEmpty.java
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-public class ClassInitializerEmpty {
-
-  static {
-  }
-
-  public static void main(String[] args) {
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index 8f465ff..de0565f 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -7,6 +7,8 @@
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.benchmarks.BenchmarkResults;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.profile.art.ArtProfileConsumer;
+import com.android.tools.r8.profile.art.ArtProfileProvider;
 import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
@@ -29,6 +31,7 @@
   }
 
   private StringBuilder proguardMapOutputBuilder = null;
+  private boolean enableMissingLibraryApiModeling = true;
 
   @Override
   public boolean isD8TestBuilder() {
@@ -80,6 +83,7 @@
       BenchmarkResults benchmarkResults)
       throws CompilationFailedException {
     libraryDesugaringTestConfiguration.configure(builder);
+    builder.setEnableExperimentalMissingLibraryApiModeling(enableMissingLibraryApiModeling);
     ToolHelper.runAndBenchmarkD8(builder, optionsConsumer, benchmarkResults);
     return new D8TestCompileResult(
         getState(),
@@ -130,6 +134,12 @@
     return self();
   }
 
+  public D8TestBuilder addArtProfileForRewriting(
+      ArtProfileProvider artProfileProvider, ArtProfileConsumer residualArtProfileConsumer) {
+    builder.addArtProfileForRewriting(artProfileProvider, residualArtProfileConsumer);
+    return self();
+  }
+
   public D8TestBuilder addStartupProfileProviders(
       StartupProfileProvider... startupProfileProviders) {
     builder.addStartupProfileProviders(startupProfileProviders);
diff --git a/src/test/java/com/android/tools/r8/D8TestCompileResult.java b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
index 6d7634c..84550ca 100644
--- a/src/test/java/com/android/tools/r8/D8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
@@ -52,6 +52,6 @@
 
   @Override
   public D8TestRunResult createRunResult(TestRuntime runtime, ProcessResult result) {
-    return new D8TestRunResult(app, runtime, result, proguardMap);
+    return new D8TestRunResult(app, runtime, result, proguardMap, state);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/D8TestRunResult.java b/src/test/java/com/android/tools/r8/D8TestRunResult.java
index 4e54359..313a39e 100644
--- a/src/test/java/com/android/tools/r8/D8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/D8TestRunResult.java
@@ -17,8 +17,12 @@
   private final String proguardMap;
 
   public D8TestRunResult(
-      AndroidApp app, TestRuntime runtime, ProcessResult result, String proguardMap) {
-    super(app, runtime, result);
+      AndroidApp app,
+      TestRuntime runtime,
+      ProcessResult result,
+      String proguardMap,
+      TestState state) {
+    super(app, runtime, result, state);
     this.proguardMap = proguardMap;
   }
 
diff --git a/src/test/java/com/android/tools/r8/DXTestCompileResult.java b/src/test/java/com/android/tools/r8/DXTestCompileResult.java
index a3cf644..d333632 100644
--- a/src/test/java/com/android/tools/r8/DXTestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/DXTestCompileResult.java
@@ -41,6 +41,6 @@
 
   @Override
   public DXTestRunResult createRunResult(TestRuntime runtime, ProcessResult result) {
-    return new DXTestRunResult(app, runtime, result);
+    return new DXTestRunResult(app, runtime, result, state);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/DXTestRunResult.java b/src/test/java/com/android/tools/r8/DXTestRunResult.java
index b395e90..23ba769 100644
--- a/src/test/java/com/android/tools/r8/DXTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/DXTestRunResult.java
@@ -9,8 +9,9 @@
 
 public class DXTestRunResult extends SingleTestRunResult<DXTestRunResult> {
 
-  public DXTestRunResult(AndroidApp app, TestRuntime runtime, ProcessResult result) {
-    super(app, runtime, result);
+  public DXTestRunResult(
+      AndroidApp app, TestRuntime runtime, ProcessResult result, TestState state) {
+    super(app, runtime, result, state);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java b/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
index 145a48c..6918607 100644
--- a/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
@@ -13,8 +13,9 @@
 
 public class Dex2OatTestRunResult extends SingleTestRunResult<Dex2OatTestRunResult> {
 
-  public Dex2OatTestRunResult(AndroidApp app, TestRuntime runtime, ProcessResult result) {
-    super(app, runtime, result);
+  public Dex2OatTestRunResult(
+      AndroidApp app, TestRuntime runtime, ProcessResult result, TestState state) {
+    super(app, runtime, result, state);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java b/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java
index 8c2623a..3212c9d 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java
@@ -73,6 +73,6 @@
 
   @Override
   protected ExternalR8TestRunResult createRunResult(TestRuntime runtime, ProcessResult result) {
-    return new ExternalR8TestRunResult(app, outputJar, proguardMap, runtime, result);
+    return new ExternalR8TestRunResult(app, outputJar, proguardMap, runtime, result, state);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestRunResult.java b/src/test/java/com/android/tools/r8/ExternalR8TestRunResult.java
index 469e5b5..1a9bfbd 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestRunResult.java
@@ -23,8 +23,9 @@
       Path outputJar,
       String proguardMap,
       TestRuntime runtime,
-      ProcessResult result) {
-    super(app, runtime, result);
+      ProcessResult result,
+      TestState state) {
+    super(app, runtime, result, state);
     this.outputJar = outputJar;
     this.proguardMap = proguardMap;
   }
diff --git a/src/test/java/com/android/tools/r8/GenerateMainDexListRunResult.java b/src/test/java/com/android/tools/r8/GenerateMainDexListRunResult.java
index 853c057..9f4b5c5 100644
--- a/src/test/java/com/android/tools/r8/GenerateMainDexListRunResult.java
+++ b/src/test/java/com/android/tools/r8/GenerateMainDexListRunResult.java
@@ -17,7 +17,7 @@
   List<String> mainDexList;
 
   public GenerateMainDexListRunResult(List<String> mainDexList, TestState state) {
-    super(null, null, null);
+    super(null, null, null, state);
     this.mainDexList = mainDexList;
     this.state = state;
   }
diff --git a/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java b/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java
index c0665cf..b9f008f 100644
--- a/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java
@@ -55,11 +55,6 @@
   }
 
   @Override
-  public DebugTestConfig debugConfig() {
-    throw new Unimplemented("No support for debug configuration");
-  }
-
-  @Override
   public GenerateMainDexListTestBuilder addRunClasspathFiles(Collection<Path> files) {
     throw new Unimplemented("No support for run class path");
   }
diff --git a/src/test/java/com/android/tools/r8/IntermediateCfD8TestBuilder.java b/src/test/java/com/android/tools/r8/IntermediateCfD8TestBuilder.java
index d018732..d3ff163 100644
--- a/src/test/java/com/android/tools/r8/IntermediateCfD8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/IntermediateCfD8TestBuilder.java
@@ -50,11 +50,6 @@
   }
 
   @Override
-  public DebugTestConfig debugConfig() {
-    throw new Unimplemented("Unsupported debug config as of now...");
-  }
-
-  @Override
   public IntermediateCfD8TestBuilder addProgramFiles(Collection<Path> files) {
     cf2cf.addProgramFiles(files);
     return self();
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
index 718cf4d..e1ab7a6 100644
--- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -4,8 +4,6 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.debug.CfDebugTestConfig;
-import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApp;
@@ -63,12 +61,7 @@
     ProcessResult result =
         ToolHelper.runJava(
             runtime.asCf(), vmArguments, classpath, ObjectArrays.concat(mainClass, args));
-    return new JvmTestRunResult(builder.build(), runtime, result);
-  }
-
-  @Override
-  public DebugTestConfig debugConfig() {
-    return new CfDebugTestConfig().addPaths(classpath);
+    return new JvmTestRunResult(builder.build(), runtime, result, getState());
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/JvmTestRunResult.java b/src/test/java/com/android/tools/r8/JvmTestRunResult.java
index fdd2580..2c911d2 100644
--- a/src/test/java/com/android/tools/r8/JvmTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/JvmTestRunResult.java
@@ -9,8 +9,9 @@
 
 public class JvmTestRunResult extends SingleTestRunResult<JvmTestRunResult> {
 
-  public JvmTestRunResult(AndroidApp app, TestRuntime runtime, ProcessResult result) {
-    super(app, runtime, result);
+  public JvmTestRunResult(
+      AndroidApp app, TestRuntime runtime, ProcessResult result, TestState state) {
+    super(app, runtime, result, state);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/L8TestBuilder.java b/src/test/java/com/android/tools/r8/L8TestBuilder.java
index 3050989..4a22769 100644
--- a/src/test/java/com/android/tools/r8/L8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/L8TestBuilder.java
@@ -11,6 +11,8 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.profile.art.ArtProfileConsumer;
+import com.android.tools.r8.profile.art.ArtProfileProvider;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidAppConsumers;
 import com.android.tools.r8.utils.ConsumerUtils;
@@ -33,6 +35,7 @@
 
   private final AndroidApiLevel apiLevel;
   private final Backend backend;
+  private final L8Command.Builder l8Builder;
   private final TestState state;
 
   private CompilationMode mode = CompilationMode.RELEASE;
@@ -50,6 +53,7 @@
     this.apiLevel = apiLevel;
     this.backend = backend;
     this.state = state;
+    this.l8Builder = L8Command.builder(state.getDiagnosticsHandler());
   }
 
   public static L8TestBuilder create(AndroidApiLevel apiLevel, Backend backend, TestState state) {
@@ -167,15 +171,14 @@
       throws IOException, CompilationFailedException, ExecutionException {
     // We wrap exceptions in a RuntimeException to call this from a lambda.
     AndroidAppConsumers sink = new AndroidAppConsumers();
-    L8Command.Builder l8Builder =
-        L8Command.builder(state.getDiagnosticsHandler())
-            .addProgramFiles(programFiles)
-            .addLibraryFiles(getLibraryFiles())
-            .setMode(mode)
-            .setIncludeClassesChecksum(true)
-            .addDesugaredLibraryConfiguration(desugaredLibrarySpecification)
-            .setMinApiLevel(apiLevel.getLevel())
-            .setProgramConsumer(computeProgramConsumer(sink));
+    l8Builder
+        .addProgramFiles(programFiles)
+        .addLibraryFiles(getLibraryFiles())
+        .setMode(mode)
+        .setIncludeClassesChecksum(true)
+        .addDesugaredLibraryConfiguration(desugaredLibrarySpecification)
+        .setMinApiLevel(apiLevel.getLevel())
+        .setProgramConsumer(computeProgramConsumer(sink));
     addProgramClassFileData(l8Builder);
     Path mapping = null;
     ImmutableList<String> allKeepRules = null;
@@ -264,4 +267,10 @@
   private Collection<Path> getLibraryFiles() {
     return libraryFiles;
   }
+
+  public L8TestBuilder addArtProfileForRewriting(
+      ArtProfileProvider artProfileProvider, ArtProfileConsumer residualArtProfileConsumer) {
+    l8Builder.addArtProfileForRewriting(artProfileProvider, residualArtProfileConsumer);
+    return this;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index dbfd4c6..9b80d3b 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -280,11 +280,6 @@
   }
 
   @Override
-  public DebugTestConfig debugConfig() {
-    throw new Unimplemented("No support for debug config");
-  }
-
-  @Override
   public ProguardTestBuilder addOptionsModification(Consumer<InternalOptions> optionsConsumer) {
     throw new Unimplemented("No support for changing internal options");
   }
diff --git a/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java b/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
index faf07a6..01cc662 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
@@ -63,6 +63,6 @@
 
   @Override
   public ProguardTestRunResult createRunResult(TestRuntime runtime, ProcessResult result) {
-    return new ProguardTestRunResult(app, runtime, result, proguardMap);
+    return new ProguardTestRunResult(app, runtime, result, proguardMap, state);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ProguardTestRunResult.java b/src/test/java/com/android/tools/r8/ProguardTestRunResult.java
index c5cccbe..4c5f121 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestRunResult.java
@@ -18,8 +18,12 @@
   private final String proguardMap;
 
   public ProguardTestRunResult(
-      AndroidApp app, TestRuntime runtime, ProcessResult result, String proguardMap) {
-    super(app, runtime, result);
+      AndroidApp app,
+      TestRuntime runtime,
+      ProcessResult result,
+      String proguardMap,
+      TestState state) {
+    super(app, runtime, result, state);
     this.proguardMap = proguardMap;
   }
 
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 1c7a5ea..ac12923 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -905,7 +905,7 @@
   @Test
   public void defaultApiModelingState() throws Exception {
     ApiModelTestingOptions options = parse("").getInternalOptions().apiModelingOptions();
-    assertTrue(options.enableApiCallerIdentification);
+    assertTrue(options.isApiCallerIdentificationEnabled());
     assertTrue(options.enableOutliningOfMethods);
     assertTrue(options.enableStubbingOfClasses);
   }
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 7b5d60b..f3f7d40 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -190,6 +190,8 @@
         .withProguardCompatibilityMode(enableProguardCompatibilityMode)
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
+        .withBuilderTransformation(
+            builder -> builder.setEnableExperimentalMissingLibraryApiModeling(true))
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
@@ -233,6 +235,8 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
+            builder -> builder.setEnableExperimentalMissingLibraryApiModeling(true))
+        .withBuilderTransformation(
             b ->
                 b.addProguardConfiguration(
                     getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown()))
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 53ff395..a844c1f 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -15,6 +15,8 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.profile.art.ArtProfileConsumer;
+import com.android.tools.r8.profile.art.ArtProfileProvider;
 import com.android.tools.r8.shaking.CheckEnumUnboxedRule;
 import com.android.tools.r8.shaking.CollectingGraphConsumer;
 import com.android.tools.r8.shaking.KeepUnusedReturnValueRule;
@@ -64,6 +66,7 @@
 
   private AllowedDiagnosticMessages allowedDiagnosticMessages = AllowedDiagnosticMessages.NONE;
   private boolean allowUnusedProguardConfigurationRules = false;
+  private boolean enableMissingLibraryApiModeling = true;
   private CollectingGraphConsumer graphConsumer = null;
   private List<String> keepRules = new ArrayList<>();
   private List<Path> mainDexRulesFiles = new ArrayList<>();
@@ -138,6 +141,7 @@
     ToolHelper.addSyntheticProguardRulesConsumerForTesting(
         builder, rules -> box.syntheticProguardRules = rules);
     libraryDesugaringTestConfiguration.configure(builder);
+    builder.setEnableExperimentalMissingLibraryApiModeling(enableMissingLibraryApiModeling);
     ToolHelper.runAndBenchmarkR8WithoutResult(
         builder,
         optionsConsumer.andThen(
@@ -778,6 +782,12 @@
     return self();
   }
 
+  public T addArtProfileForRewriting(
+      ArtProfileProvider artProfileProvider, ArtProfileConsumer residualArtProfileConsumer) {
+    builder.addArtProfileForRewriting(artProfileProvider, residualArtProfileConsumer);
+    return self();
+  }
+
   public T addStartupProfileProviders(StartupProfileProvider... startupProfileProviders) {
     builder.addStartupProfileProviders(startupProfileProviders);
     return self();
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index be01cef..2d25b7c 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -151,7 +151,7 @@
 
   @Override
   public R8TestRunResult createRunResult(TestRuntime runtime, ProcessResult result) {
-    return new R8TestRunResult(app, runtime, result, proguardMap, this::graphInspector);
+    return new R8TestRunResult(app, runtime, result, proguardMap, this::graphInspector, state);
   }
 
   public R8TestCompileResult addFeatureSplitsToRunClasspathFiles() {
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index 716971d..8734544 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -30,8 +30,9 @@
       TestRuntime runtime,
       ProcessResult result,
       String proguardMap,
-      GraphInspectorSupplier graphInspector) {
-    super(app, runtime, result);
+      GraphInspectorSupplier graphInspector,
+      TestState state) {
+    super(app, runtime, result, state);
     this.proguardMap = proguardMap;
     this.graphInspector = graphInspector;
   }
diff --git a/src/test/java/com/android/tools/r8/SingleTestRunResult.java b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
index 68e132a..e94bfcf 100644
--- a/src/test/java/com/android/tools/r8/SingleTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
@@ -10,12 +10,14 @@
 
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
 import java.io.PrintStream;
+import java.nio.file.Path;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
@@ -23,12 +25,15 @@
 
 public abstract class SingleTestRunResult<RR extends SingleTestRunResult<RR>>
     extends TestRunResult<RR> {
+  private final TestState state;
   protected final AndroidApp app;
   private final TestRuntime runtime;
   private final ProcessResult result;
   private boolean executedSatisfyingRuntime = false;
 
-  public SingleTestRunResult(AndroidApp app, TestRuntime runtime, ProcessResult result) {
+  public SingleTestRunResult(
+      AndroidApp app, TestRuntime runtime, ProcessResult result, TestState state) {
+    this.state = state;
     this.app = app;
     this.runtime = runtime;
     this.result = result;
@@ -38,6 +43,10 @@
     return false;
   }
 
+  public TestState getState() {
+    return state;
+  }
+
   public AndroidApp app() {
     return app;
   }
@@ -219,4 +228,13 @@
     }
     return self();
   }
+
+  public <E extends Throwable> RR debugger(ThrowingConsumer<DebugTestConfig, E> consumer)
+      throws E, IOException {
+    Path out = state.getNewTempFolder().resolve("out.zip");
+    app.writeToZip(out, runtime.isCf() ? OutputMode.ClassFile : OutputMode.DexIndexed);
+    DebugTestConfig config = DebugTestConfig.create(runtime, out);
+    consumer.accept(config);
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 4f8181d..a575af1 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -46,6 +46,7 @@
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.profile.art.ArtProfileCollection;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index 52d84fc..2faa8fd 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.TestBase.Backend;
-import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ImmutableList;
@@ -99,8 +98,6 @@
     return run(runtime, mainClass.getTypeName(), args);
   }
 
-  public abstract DebugTestConfig debugConfig();
-
   public abstract T addProgramFiles(Collection<Path> files);
 
   public abstract T addProgramClassFileData(Collection<byte[]> classes);
diff --git a/src/test/java/com/android/tools/r8/TestBuilderCollection.java b/src/test/java/com/android/tools/r8/TestBuilderCollection.java
index 0192832..9f59e8e 100644
--- a/src/test/java/com/android/tools/r8/TestBuilderCollection.java
+++ b/src/test/java/com/android/tools/r8/TestBuilderCollection.java
@@ -33,11 +33,6 @@
   }
 
   @Override
-  public DebugTestConfig debugConfig() {
-    throw new Unimplemented("Unsupported debug config as of now...");
-  }
-
-  @Override
   public T addProgramFiles(Collection<Path> files) {
     return forEach(b -> b.addProgramFiles(files));
   }
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index b15e669..323cf72 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -19,9 +19,7 @@
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.benchmarks.BenchmarkResults;
-import com.android.tools.r8.debug.CfDebugTestConfig;
 import com.android.tools.r8.debug.DebugTestConfig;
-import com.android.tools.r8.debug.DexDebugTestConfig;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -605,20 +603,22 @@
     return disassemble(System.out);
   }
 
+  @Deprecated
   public DebugTestConfig debugConfig() {
+    return debugConfig(
+        getBackend().isCf()
+            ? TestRuntime.getDefaultCfRuntime()
+            : new DexRuntime(ToolHelper.getDexVm()));
+  }
+
+  public DebugTestConfig debugConfig(TestRuntime runtime) {
+    assert runtime.getBackend() == getBackend();
     // Rethrow exceptions since debug config is usually used in a delayed wrapper which
     // does not declare exceptions.
     try {
       Path out = state.getNewTempFolder().resolve("out.zip");
       app.writeToZip(out, getOutputMode());
-      switch (getBackend()) {
-        case CF:
-          return new CfDebugTestConfig().addPaths(out);
-        case DEX:
-          return new DexDebugTestConfig().addPaths(out);
-        default:
-          throw new Unreachable();
-      }
+      return DebugTestConfig.create(runtime, out);
     } catch (IOException e) {
       throw new RuntimeException(e);
     }
@@ -687,7 +687,8 @@
     Path jarFile = tmp.resolve("out.jar");
     Path oatFile = tmp.resolve("out.oat");
     app.writeToZip(jarFile, OutputMode.DexIndexed);
-    return new Dex2OatTestRunResult(app, runtime, ToolHelper.runDex2OatRaw(jarFile, oatFile, vm));
+    return new Dex2OatTestRunResult(
+        app, runtime, ToolHelper.runDex2OatRaw(jarFile, oatFile, vm), state);
   }
 
   public CR benchmarkCodeSize(BenchmarkResults results) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 141f879..165cd83 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -342,17 +342,6 @@
     return compile().run(runtime, mainClass, args);
   }
 
-  @Override
-  public DebugTestConfig debugConfig() {
-    // Rethrow exceptions since debug config is usually used in a delayed wrapper which
-    // does not declare exceptions.
-    try {
-      return compile().debugConfig();
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
   public T setMode(CompilationMode mode) {
     builder.setMode(mode);
     return self();
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
index c4fc8c4..dd84b92 100644
--- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -25,6 +25,8 @@
   // Built via the methods found below. Defaults to no applicable parameters, i.e., the emtpy set.
   private Predicate<TestParameters> filter = param -> false;
   private boolean hasDexRuntimeFilter = false;
+  // TODO(b/245066448): Enable bot with testing new art master.
+  private boolean allowMaster = System.getProperty("com.android.tools.r8.artmaster") != null;
 
   TestParametersBuilder() {}
 
@@ -40,7 +42,10 @@
   private TestParametersBuilder withDexRuntimeFilter(Predicate<DexVm.Version> predicate) {
     hasDexRuntimeFilter = true;
     return withFilter(
-        p -> p.isDexRuntime() && predicate.test(p.getRuntime().asDex().getVm().getVersion()));
+        p ->
+            p.isDexRuntime()
+                && (allowMaster || p.getDexRuntimeVersion().isOlderThan(DexVm.Version.MASTER))
+                && predicate.test(p.getDexRuntimeVersion()));
   }
 
   public TestParametersBuilder withNoneRuntime() {
@@ -130,12 +135,13 @@
 
   /** Add all available DEX runtimes including master. */
   public TestParametersBuilder withDexRuntimesIncludingMaster() {
-    return withDexRuntimeFilter(vm -> true);
+    this.allowMaster = true;
+    return withDexRuntimes();
   }
 
   /** Add all available DEX runtimes except master. */
   public TestParametersBuilder withDexRuntimes() {
-    return withDexRuntimeFilter(vm -> vm != DexVm.Version.MASTER);
+    return withDexRuntimeFilter(vm -> true);
   }
 
   public TestParametersBuilder withDexRuntimesAndAllApiLevels() {
diff --git a/src/test/java/com/android/tools/r8/TestRuntime.java b/src/test/java/com/android/tools/r8/TestRuntime.java
index adb87d2..3524d63 100644
--- a/src/test/java/com/android/tools/r8/TestRuntime.java
+++ b/src/test/java/com/android/tools/r8/TestRuntime.java
@@ -19,6 +19,10 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
 
 // Base class for the runtime structure in the test parameters.
 public abstract class TestRuntime {
@@ -375,6 +379,26 @@
     }
   }
 
+  public <T> T match(Function<CfRuntime, T> onCf, BiFunction<DexRuntime, DexVm.Version, T> onDex) {
+    if (isCf()) {
+      return onCf.apply(asCf());
+    }
+    if (isDex()) {
+      return onDex.apply(asDex(), asDex().getVersion());
+    }
+    throw new Unreachable();
+  }
+
+  public void match(Consumer<CfRuntime> onCf, BiConsumer<DexRuntime, DexVm.Version> onDex) {
+    if (isCf()) {
+      onCf.accept(asCf());
+    } else if (isDex()) {
+      onDex.accept(asDex(), asDex().getVersion());
+    } else {
+      throw new Unreachable();
+    }
+  }
+
   public boolean isDex() {
     return false;
   }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index f31c1ae..5fb11f1 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.ToolHelper.DexVm.Kind;
 import com.android.tools.r8.benchmarks.BenchmarkResults;
-import com.android.tools.r8.desugar.desugaredlibrary.jdk11.DesugaredLibraryJDK11Undesugarer;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.CustomConversionVersion;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.errors.Unreachable;
@@ -194,11 +193,6 @@
         : Paths.get(LIBS_DIR, "library_desugar_conversions.jar");
   }
 
-  public static Path getUndesugaredJdk11LibJarForTesting() {
-    return DesugaredLibraryJDK11Undesugarer.undesugaredJarJDK11(
-        Paths.get("build/libs"), DESUGARED_JDK_11_LIB_JAR);
-  }
-
   public static boolean isLocalDevelopment() {
     return System.getProperty("local_development", "0").equals("1");
   }
@@ -274,7 +268,7 @@
       V6_0_1("6.0.1"),
       V7_0_0("7.0.0"),
       V8_1_0("8.1.0"),
-      // TODO(b//204855476): Remove DEFAULT.
+      // TODO(b/204855476): Remove DEFAULT.
       DEFAULT("default"),
       V9_0_0("9.0.0"),
       V10_0_0("10.0.0"),
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
index 7de78a2..29c9709 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
@@ -46,7 +46,7 @@
           .resolve("new_api_database.ser");
 
   // Update the API_LEVEL below to have the database generated for a new api level.
-  private static final AndroidApiLevel API_LEVEL = AndroidApiLevel.S;
+  private static final AndroidApiLevel API_LEVEL = AndroidApiLevel.LATEST;
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -103,9 +103,9 @@
               }));
         });
     // These numbers will change when updating api-versions.xml
-    assertEquals(5065, parsedApiClasses.size());
-    assertEquals(26492, numberOfFields.get());
-    assertEquals(40475, numberOfMethods.get());
+    assertEquals(5272, parsedApiClasses.size());
+    assertEquals(27868, numberOfFields.get());
+    assertEquals(42268, numberOfMethods.get());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelCovariantReturnTypeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelCovariantReturnTypeTest.java
index 524147f..353c709 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelCovariantReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelCovariantReturnTypeTest.java
@@ -42,6 +42,7 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Main.class)
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
         .apply(
             addTracedApiReferenceLevelCallBack(
                 (method, apiLevel) -> {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeProgramDefinedDuplicateTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeProgramDefinedDuplicateTest.java
index 0a72589..c8c4fe2 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeProgramDefinedDuplicateTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeProgramDefinedDuplicateTest.java
@@ -59,6 +59,7 @@
         .setMinApi(parameters.getApiLevel())
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(ApiModelingTestHelper::disableOutlining)
         .apply(setMockApiLevelForClass(LibraryClass.class, mockLevel))
         .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, mockLevel))
         .apply(setMockApiLevelForMethod(LibraryClass.class.getDeclaredMethod("foo"), mockLevel))
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeTest.java
index 5798d5a..dea3e93 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeTest.java
@@ -60,6 +60,7 @@
         .setMinApi(parameters.getApiLevel())
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(ApiModelingTestHelper::disableOutlining)
         .apply(setMockApiLevelForClass(LibraryClass.class, mockLevel))
         .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, mockLevel))
         .apply(setMockApiLevelForMethod(LibraryClass.class.getDeclaredMethod("foo"), mockLevel))
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoClassInliningFieldTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoClassInliningFieldTest.java
index 5392a35..27ba7ed 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoClassInliningFieldTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoClassInliningFieldTest.java
@@ -52,6 +52,7 @@
         .apply(setMockApiLevelForClass(Api.class, AndroidApiLevel.L_MR1))
         .apply(setMockApiLevelForDefaultInstanceInitializer(Api.class, AndroidApiLevel.L_MR1))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
         .compile()
         .inspect(
             inspector -> {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java
index ecbd92e..8ef728e 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java
@@ -53,6 +53,7 @@
         .apply(setMockApiLevelForClass(Api.class, L_MR1))
         .apply(setMockApiLevelForDefaultInstanceInitializer(Api.class, L_MR1))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
         .compile()
         .inspect(
             inspector ->
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelStaticFieldTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelStaticFieldTest.java
index f4465a4..8a5904a 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelStaticFieldTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelStaticFieldTest.java
@@ -49,6 +49,7 @@
         .enableNoHorizontalClassMergingAnnotations()
         .apply(setMockApiLevelForField(apiField, AndroidApiLevel.L_MR1))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
         .compile()
         .inspect(
             inspector ->
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfTryCatchReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfTryCatchReferenceTest.java
index 9565973..6bd30af 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfTryCatchReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfTryCatchReferenceTest.java
@@ -47,6 +47,7 @@
         .apply(setMockApiLevelForClass(ApiException.class, exceptionApiLevel))
         .apply(setMockApiLevelForDefaultInstanceInitializer(ApiException.class, exceptionApiLevel))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
         .enableInliningAnnotations()
         .addHorizontallyMergedClassesInspector(
             horizontallyMergedClassesInspector -> {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelObjectInitTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelObjectInitTest.java
index e5118ab..cffda67 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelObjectInitTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelObjectInitTest.java
@@ -63,6 +63,7 @@
         .addLibraryClasses(LibraryClass.class)
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters.getApiLevel())
+        .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
         .apply(setMockApiLevelForClass(LibraryClass.class, AndroidApiLevel.L))
         .apply(setMockApiLevelForMethod(declaredConstructor, AndroidApiLevel.L))
         .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, AndroidApiLevel.N))
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java
index 57eaa67..e826dd7 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.apimodel;
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
-import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
@@ -60,7 +59,6 @@
         .setMinApi(parameters.getApiLevel())
         .addAndroidBuildVersion()
         .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
-        .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, classApiLevel))
         .apply(setMockApiLevelForMethod(addedOn23(), methodApiLevel))
         // TODO(b/213552119): Remove when enabled by default.
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
@@ -162,7 +160,7 @@
   // Only present from api level 19.
   public static class LibraryClass {
 
-    public void addedOn23() {
+    public static void addedOn23() {
       System.out.println("LibraryClass::addedOn23");
     }
   }
@@ -171,12 +169,9 @@
 
     @NeverInline
     public static void test() {
-      if (AndroidBuildVersion.VERSION >= 19) {
-        LibraryClass libraryClass = new LibraryClass();
-        if (AndroidBuildVersion.VERSION >= 23) {
-          libraryClass.addedOn23();
-          libraryClass.addedOn23();
-        }
+      if (AndroidBuildVersion.VERSION >= 23) {
+        LibraryClass.addedOn23();
+        LibraryClass.addedOn23();
       }
       System.out.println("Hello World");
     }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java
index 9793b9b..978d068 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java
@@ -35,7 +35,6 @@
 public class ApiModelOutlineHorizontalMergingTest extends TestBase {
 
   private final AndroidApiLevel libraryClassApiLevel = AndroidApiLevel.K;
-  private final AndroidApiLevel otherLibraryClassApiLevel = AndroidApiLevel.K;
   private final AndroidApiLevel firstMethodApiLevel = AndroidApiLevel.M;
   private final AndroidApiLevel secondMethodApiLevel = AndroidApiLevel.O_MR1;
 
@@ -62,10 +61,10 @@
         .apply(
             setMockApiLevelForMethod(
                 LibraryClass.class.getMethod("addedOn27"), secondMethodApiLevel))
-        .apply(setMockApiLevelForClass(OtherLibraryClass.class, otherLibraryClassApiLevel))
+        .apply(setMockApiLevelForClass(OtherLibraryClass.class, libraryClassApiLevel))
         .apply(
             setMockApiLevelForDefaultInstanceInitializer(
-                OtherLibraryClass.class, otherLibraryClassApiLevel))
+                OtherLibraryClass.class, libraryClassApiLevel))
         .apply(
             setMockApiLevelForMethod(
                 OtherLibraryClass.class.getMethod("addedOn23"), firstMethodApiLevel))
@@ -101,7 +100,11 @@
         .inspect(
             inspector -> {
               // TODO(b/187675788): Update when horizontal merging is enabled for D8 for debug mode.
-              if (parameters.getApiLevel().isLessThan(firstMethodApiLevel)) {
+              if (parameters.getApiLevel().isLessThan(libraryClassApiLevel)) {
+                // We have generated 4 outlines two having api level 23 and two having api level 27
+                // and 2 outlines for each instance initializer.
+                assertEquals(11, inspector.allClasses().size());
+              } else if (parameters.getApiLevel().isLessThan(firstMethodApiLevel)) {
                 // We have generated 4 outlines two having api level 23 and two having api level 27.
                 assertEquals(7, inspector.allClasses().size());
               } else if (parameters.getApiLevel().isLessThan(secondMethodApiLevel)) {
@@ -166,8 +169,12 @@
       assertEquals(3, inspector.allClasses().size());
     } else if (parameters.getApiLevel().isLessThan(firstMethodApiLevel)) {
       // We have generated 4 outlines two having api level 23 and two having api level 27.
+      // If less than the library api level then we have synthesized two instance initializer
+      // outlines as well.
       // Check that the levels are horizontally merged.
-      assertEquals(5, inspector.allClasses().size());
+      assertEquals(
+          parameters.getApiLevel().isLessThan(libraryClassApiLevel) ? 6 : 5,
+          inspector.allClasses().size());
       assertEquals(2, outlinedAddedOn23.size());
       assertTrue(
           outlinedAddedOn23.stream()
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceInitializerSuperTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceInitializerSuperTest.java
new file mode 100644
index 0000000..a362bd3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceInitializerSuperTest.java
@@ -0,0 +1,165 @@
+// Copyright (c) 2022, 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.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ApiModelOutlineInstanceInitializerSuperTest extends TestBase {
+
+  private static final AndroidApiLevel classApiLevel = AndroidApiLevel.M;
+
+  private static final String[] EXPECTED = new String[] {"Hello ", "World!"};
+
+  @Parameter public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    testBuilder
+        .addLibraryClasses(LibraryClass.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .addProgramClasses(Main.class, ProgramExtendsLibraryClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .addAndroidBuildVersion()
+        .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
+        .apply(
+            setMockApiLevelForMethod(
+                LibraryClass.class.getDeclaredConstructor(String.class), classApiLevel))
+        .apply(setMockApiLevelForMethod(LibraryClass.class.getMethod("print"), classApiLevel))
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+        .apply(ApiModelingTestHelper::disableStubbingOfClasses);
+  }
+
+  public boolean addToBootClasspath() {
+    return parameters.isDexRuntime()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(classApiLevel);
+  }
+
+  @Test
+  public void testD8Debug() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .inspect(inspector -> inspect(inspector, false))
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .inspect(inspector -> inspect(inspector, false))
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .apply(this::setupTestBuilder)
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRules(ProgramExtendsLibraryClass.class)
+        .addDontObfuscate()
+        .compile()
+        .inspect(inspector -> inspect(inspector, true))
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  private void inspect(CodeInspector inspector, boolean isR8) throws Exception {
+    // Because each of the outline context also have a super call, R8 will inline back the outline
+    // because the super call has the same api level as the outlinee.
+    verifyThat(inspector, parameters, LibraryClass.class.getMethod("print"))
+        .isOutlinedFromUntil(
+            ProgramExtendsLibraryClass.class.getMethod("print"),
+            isR8 ? AndroidApiLevel.B : classApiLevel);
+    verifyThat(inspector, parameters, LibraryClass.class.getDeclaredConstructor(String.class))
+        .isOutlinedFromUntil(
+            ProgramExtendsLibraryClass.class.getDeclaredConstructor(String.class),
+            isR8 ? AndroidApiLevel.B : classApiLevel);
+  }
+
+  private void checkOutput(SingleTestRunResult<?> runResult) {
+    if (parameters.isDexRuntime()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(classApiLevel)) {
+      runResult.assertSuccessWithOutputLines(EXPECTED);
+    } else {
+      runResult.assertSuccessWithOutputLines("Not calling API");
+    }
+  }
+
+  // Only present from api level 23.
+  public static class LibraryClass {
+
+    private final String arg;
+
+    public LibraryClass(String arg) {
+      this.arg = arg;
+    }
+
+    public void print() {
+      System.out.println(arg);
+    }
+  }
+
+  public static class ProgramExtendsLibraryClass extends LibraryClass {
+
+    private final LibraryClass otherArg;
+
+    public ProgramExtendsLibraryClass(String arg) {
+      super(arg); // <-- this cannot be outlined
+      otherArg = new LibraryClass("World!"); // <-- this should be outlined.
+    }
+
+    @Override
+    public void print() {
+      super.print();
+      otherArg.print();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      if (AndroidBuildVersion.VERSION >= 23) {
+        new ProgramExtendsLibraryClass("Hello ").print();
+      } else {
+        System.out.println("Not calling API");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceInitializerTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceInitializerTest.java
index ea08c59..b0a5fb7 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceInitializerTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceInitializerTest.java
@@ -113,11 +113,11 @@
   private void inspect(CodeInspector inspector, boolean isR8) throws Exception {
     Method mainMethod = Main.class.getMethod("main", String[].class);
     verifyThat(inspector, parameters, Argument.class.getDeclaredConstructor(String.class))
-        .isOutlinedFromUntil(mainMethod, AndroidApiLevel.B);
+        .isOutlinedFromUntil(mainMethod, classApiLevel);
     verifyThat(inspector, parameters, LibraryClass.class.getDeclaredConstructor(Argument.class))
-        .isOutlinedFromUntil(mainMethod, AndroidApiLevel.B);
+        .isOutlinedFromUntil(mainMethod, classApiLevel);
     verifyThat(inspector, parameters, LibraryClass.class.getMethod("print"))
-        .isOutlinedFromUntil(mainMethod, isR8 ? AndroidApiLevel.B : classApiLevel);
+        .isOutlinedFromUntil(mainMethod, classApiLevel);
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java
index 0b2f270..3504527 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.apimodel;
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
-import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 import static org.junit.Assume.assumeTrue;
@@ -55,8 +54,9 @@
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(ApiModelingTestHelper::enableStubbingOfClasses)
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+        // We only model the class and not the default initializer, otherwise we outline the new
+        // instance call and remove the last reference in non-outlined code.
         .apply(setMockApiLevelForClass(LibraryClass.class, libraryClassLevel))
-        .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, libraryClassLevel))
         .apply(setMockApiLevelForMethod(apiMethod(), libraryMethodLevel));
   }
 
@@ -123,7 +123,7 @@
   public static class LibraryClass {
 
     // Only present from api level 30
-    public void foo() {
+    public static void foo() {
       System.out.println("LibraryClass::foo");
     }
   }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java
index 21db603..22f79df 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.apimodel;
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
-import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
@@ -64,9 +63,6 @@
         .setMinApi(parameters.getApiLevel())
         .addAndroidBuildVersion()
         .apply(setMockApiLevelForClass(LibraryClass.class, initialLibraryMockLevel))
-        .apply(
-            setMockApiLevelForDefaultInstanceInitializer(
-                LibraryClass.class, initialLibraryMockLevel))
         .apply(setMockApiLevelForMethod(addedOn23(), initialLibraryMockLevel))
         .apply(setMockApiLevelForMethod(addedOn27(), finalLibraryMethodLevel))
         // TODO(b/213552119): Remove when enabled by default.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
index 178c115..016a147 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.accessesField;
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithHolderAndName;
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
@@ -28,6 +29,7 @@
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.Collections;
@@ -122,22 +124,23 @@
       TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
     compilerBuilder.addOptionsModification(
         options -> {
+          options.apiModelingOptions().enableLibraryApiModeling = true;
           options.apiModelingOptions().enableApiCallerIdentification = true;
         });
   }
 
-  static void enableStubbingOfClasses(TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
+  public static void enableStubbingOfClasses(TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
     compilerBuilder.addOptionsModification(
         options -> {
-          options.apiModelingOptions().enableApiCallerIdentification = true;
+          options.apiModelingOptions().enableLibraryApiModeling = true;
           options.apiModelingOptions().enableStubbingOfClasses = true;
         });
   }
 
-  static void enableOutliningOfMethods(TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
+  public static void enableOutliningOfMethods(TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
     compilerBuilder.addOptionsModification(
         options -> {
-          options.apiModelingOptions().enableApiCallerIdentification = true;
+          options.apiModelingOptions().enableLibraryApiModeling = true;
           options.apiModelingOptions().enableOutliningOfMethods = true;
         });
   }
@@ -167,6 +170,19 @@
         options -> options.apiModelingOptions().enableOutliningOfMethods = false);
   }
 
+  public static void disableApiCallerIdentification(
+      TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
+    compilerBuilder.addOptionsModification(
+        options -> options.apiModelingOptions().enableApiCallerIdentification = false);
+  }
+
+  public static void disableApiModeling(TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
+    disableOutliningAndStubbing(compilerBuilder);
+    disableApiCallerIdentification(compilerBuilder);
+    compilerBuilder.addOptionsModification(
+        options -> options.apiModelingOptions().enableLibraryApiModeling = false);
+  }
+
   static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>>
       ThrowableConsumer<T> addTracedApiReferenceLevelCallBack(
           BiConsumer<MethodReference, AndroidApiLevel> consumer) {
@@ -334,7 +350,7 @@
       assertThat(target, not(CodeMatchers.invokesMethod(candidate)));
     }
 
-    void isOutlinedFromUntil(Method method, AndroidApiLevel apiLevel) {
+    void isOutlinedFromUntil(Executable method, AndroidApiLevel apiLevel) {
       if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(apiLevel)) {
         isOutlinedFrom(method);
       } else {
@@ -342,7 +358,7 @@
       }
     }
 
-    void isOutlinedFrom(Method method) {
+    void isOutlinedFrom(Executable method) {
       // Check that the call is in a synthetic class.
       List<FoundMethodSubject> outlinedMethod =
           inspector.allClasses().stream()
@@ -350,18 +366,20 @@
               .filter(
                   methodSubject ->
                       methodSubject.isSynthetic()
-                          && invokesMethodWithName(methodOfInterest.getMethodName())
+                          && invokesMethodWithHolderAndName(
+                                  methodOfInterest.getHolderClass().getTypeName(),
+                                  methodOfInterest.getMethodName())
                               .matches(methodSubject))
               .collect(Collectors.toList());
       assertEquals(1, outlinedMethod.size());
       // Assert that method invokes the outline
-      MethodSubject caller = inspector.method(method);
+      MethodSubject caller = inspector.method(Reference.methodFromMethod(method));
       assertThat(caller, isPresent());
       assertThat(caller, invokesMethod(outlinedMethod.get(0)));
     }
 
-    void isNotOutlinedFrom(Method method) {
-      MethodSubject caller = inspector.method(method);
+    void isNotOutlinedFrom(Executable method) {
+      MethodSubject caller = inspector.method(Reference.methodFromMethod(method));
       assertThat(caller, isPresent());
       assertThat(caller, invokesMethodWithName(methodOfInterest.getMethodName()));
     }
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
index d08bb2f..73f2712 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
@@ -294,7 +294,7 @@
 
               // Cls1#foo and Cls2#bar should refer to Base#foo.
 
-              MethodSubject barInCls2 = cls2Subject.method("void", "bar", "java.lang.String");
+              MethodSubject barInCls2 = baseSubject.method("void", "bar", "java.lang.String");
               assertThat(barInCls2, isPresent());
 
               // Cls1#foo has been moved to Base#foo as a result of bridge hoisting.
@@ -404,7 +404,7 @@
 
               // DerivedString2#bar should refer to Base#foo.
 
-              MethodSubject barInSub = subSubject.method("void", "bar", "java.lang.String");
+              MethodSubject barInSub = baseSubject.method("void", "bar", "java.lang.String");
               assertThat(barInSub, isPresent());
 
               if (parameters.isDexRuntime()) {
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeToPackagePrivateMethodTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeToPackagePrivateMethodTest.java
index 10fc6da..449975b 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeToPackagePrivateMethodTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeToPackagePrivateMethodTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.google.common.collect.ImmutableList;
+import java.io.IOException;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -35,18 +36,7 @@
   @Test
   public void testRuntime() throws Exception {
     testForRuntime(parameters)
-        .addProgramClassFileData(
-            transformer(Main.class)
-                .replaceClassDescriptorInMethodInstructions(
-                    descriptor(A.class), TRANSFORMED_A_DESCRIPTOR)
-                .transform(),
-            transformer(A.class).setClassDescriptor(TRANSFORMED_A_DESCRIPTOR).transform(),
-            transformer(B.class)
-                .setSuper(TRANSFORMED_A_DESCRIPTOR)
-                .replaceClassDescriptorInMethodInstructions(
-                    descriptor(A.class), TRANSFORMED_A_DESCRIPTOR)
-                .setBridge(B.class.getDeclaredMethod("bridge"))
-                .transform())
+        .addProgramClassFileData(getProgramClassFileData())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
   }
@@ -54,18 +44,7 @@
   @Test
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
-        .addProgramClassFileData(
-            transformer(Main.class)
-                .replaceClassDescriptorInMethodInstructions(
-                    descriptor(A.class), TRANSFORMED_A_DESCRIPTOR)
-                .transform(),
-            transformer(A.class).setClassDescriptor(TRANSFORMED_A_DESCRIPTOR).transform(),
-            transformer(B.class)
-                .setSuper(TRANSFORMED_A_DESCRIPTOR)
-                .replaceClassDescriptorInMethodInstructions(
-                    descriptor(A.class), TRANSFORMED_A_DESCRIPTOR)
-                .setBridge(B.class.getDeclaredMethod("bridge"))
-                .transform())
+        .addProgramClassFileData(getProgramClassFileData())
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
@@ -73,11 +52,22 @@
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
-        // TODO(b/233866639): Should succeed with "A", "B".
-        .applyIf(
-            parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik(),
-            runResult -> runResult.assertSuccessWithOutputLines(EXPECTED_OUTPUT),
-            runResult -> runResult.assertSuccessWithOutputLines("A", "A"));
+        .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+  }
+
+  private List<byte[]> getProgramClassFileData() throws IOException, NoSuchMethodException {
+    return ImmutableList.of(
+        transformer(Main.class)
+            .replaceClassDescriptorInMethodInstructions(
+                descriptor(A.class), TRANSFORMED_A_DESCRIPTOR)
+            .transform(),
+        transformer(A.class).setClassDescriptor(TRANSFORMED_A_DESCRIPTOR).transform(),
+        transformer(B.class)
+            .setSuper(TRANSFORMED_A_DESCRIPTOR)
+            .replaceClassDescriptorInMethodInstructions(
+                descriptor(A.class), TRANSFORMED_A_DESCRIPTOR)
+            .setBridge(B.class.getDeclaredMethod("bridge"))
+            .transform());
   }
 
   static class Main {
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeToPackagePrivateMethodThatOverridesPublicMethodTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeToPackagePrivateMethodThatOverridesPublicMethodTest.java
new file mode 100644
index 0000000..b273533
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeToPackagePrivateMethodThatOverridesPublicMethodTest.java
@@ -0,0 +1,201 @@
+// Copyright (c) 2022, 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.bridgeremoval.hoisting;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class BridgeToPackagePrivateMethodThatOverridesPublicMethodTest extends TestBase {
+
+  private static final String TRANSFORMED_B_DESCRIPTOR = "LB;";
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public boolean removeBridgeMethodFromA;
+
+  @Parameters(name = "{0}, remove A.bridge(): {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Base.class)
+        .addProgramClassFileData(getProgramClassFileData())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(getExpectedOutput());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Base.class)
+        .addProgramClassFileData(getProgramClassFileData())
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              // Inspect Base.
+              ClassSubject baseClassSubject = inspector.clazz(Base.class);
+              assertThat(baseClassSubject, isPresent());
+
+              MethodSubject bridgeOnBaseMethodSubject =
+                  baseClassSubject.uniqueMethodWithName("bridge");
+              assertThat(bridgeOnBaseMethodSubject, isPresent());
+
+              // Inspect A. This should have the bridge method unless it is removed by the
+              // transformation.
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+
+              MethodSubject bridgeOnAMethodSubject = aClassSubject.uniqueMethodWithName("bridge");
+              assertThat(bridgeOnAMethodSubject, notIf(isPresent(), removeBridgeMethodFromA));
+
+              // Inspect B. This should not have a bridge method, as the bridge in C is not eligible
+              // for hoisting into B.
+              ClassSubject bClassSubject =
+                  inspector.clazz(DescriptorUtils.descriptorToJavaType(TRANSFORMED_B_DESCRIPTOR));
+              assertThat(bClassSubject, isPresent());
+
+              MethodSubject bridgeMethodSubject = bClassSubject.uniqueMethodWithName("bridge");
+              assertThat(bridgeMethodSubject, isAbsent());
+
+              MethodSubject testMethodSubject = bClassSubject.uniqueMethodWithName("test");
+              assertThat(testMethodSubject, isPresent());
+
+              // Inspect C. The method C.bridge() is never eligible for hoisting.
+              ClassSubject cClassSubject = inspector.clazz(C.class);
+              assertThat(cClassSubject, isPresent());
+
+              MethodSubject bridgeOnCMethodSubject = cClassSubject.uniqueMethodWithName("bridge");
+              assertThat(bridgeOnCMethodSubject, isPresent());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(getExpectedOutput());
+  }
+
+  private List<String> getExpectedOutput() {
+    if (removeBridgeMethodFromA) {
+      return ImmutableList.of("A.m()", "Base.bridge()", "C.m()", "C.m()");
+    }
+    return ImmutableList.of("A.m()", "A.bridge()", "C.m()", "C.m()");
+  }
+
+  private List<byte[]> getProgramClassFileData() throws IOException, NoSuchMethodException {
+    return ImmutableList.of(
+        transformer(Main.class)
+            .replaceClassDescriptorInMethodInstructions(
+                descriptor(B.class), TRANSFORMED_B_DESCRIPTOR)
+            .transform(),
+        transformer(A.class)
+            .applyIf(
+                removeBridgeMethodFromA, transformer -> transformer.removeMethodsWithName("bridge"))
+            .setPublic(A.class.getDeclaredMethod("m"))
+            .transform(),
+        transformer(B.class).setClassDescriptor(TRANSFORMED_B_DESCRIPTOR).transform(),
+        transformer(C.class)
+            .setSuper(TRANSFORMED_B_DESCRIPTOR)
+            .replaceClassDescriptorInMethodInstructions(
+                descriptor(B.class), TRANSFORMED_B_DESCRIPTOR)
+            .setBridge(C.class.getDeclaredMethod("bridge"))
+            .transform());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new A().callM();
+      B bAsB = System.currentTimeMillis() > 0 ? new B() : new C();
+      B cAsB = System.currentTimeMillis() > 0 ? new C() : new B();
+      Base cAsBase = System.currentTimeMillis() > 0 ? new C() : new Base();
+      bAsB.test(bAsB);
+      bAsB.test(cAsB);
+      cAsBase.bridge();
+    }
+  }
+
+  @NoVerticalClassMerging
+  public static class Base {
+
+    public void bridge() {
+      System.out.println("Base.bridge()");
+    }
+  }
+
+  @NeverClassInline
+  @NoVerticalClassMerging
+  public static class A extends Base {
+
+    @NeverInline
+    /*public*/ void m() {
+      System.out.println("A.m()");
+    }
+
+    @Override
+    public void bridge() {
+      System.out.println("A.bridge()");
+    }
+
+    @NeverInline
+    public void callM() {
+      m();
+    }
+  }
+
+  @NeverClassInline
+  @NoVerticalClassMerging
+  public static class /*otherpackage.*/ B extends A {
+
+    @NeverInline
+    public void test(B b) {
+      b.bridge();
+    }
+  }
+
+  @NeverClassInline
+  public static class C extends B {
+
+    @NeverInline
+    void m() {
+      System.out.println("C.m()");
+    }
+
+    // Not eligible for bridge hoisting, as the bridge will then dispatch to A.m instead of B.m.
+    @NeverInline
+    @Override
+    public /*bridge*/ void bridge() {
+      this.m();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/SingleBridgeToPackagePrivateMethodThatOverridesPublicMethodTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/SingleBridgeToPackagePrivateMethodThatOverridesPublicMethodTest.java
new file mode 100644
index 0000000..91ddda8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/SingleBridgeToPackagePrivateMethodThatOverridesPublicMethodTest.java
@@ -0,0 +1,182 @@
+// Copyright (c) 2022, 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.bridgeremoval.hoisting;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SingleBridgeToPackagePrivateMethodThatOverridesPublicMethodTest extends TestBase {
+
+  private static final String TRANSFORMED_B_DESCRIPTOR = "LB;";
+
+  @Parameter(0)
+  public boolean enableBridgeHoistingFromB;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, enable bridge hoisting from B: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    assumeFalse(enableBridgeHoistingFromB);
+    testForRuntime(parameters)
+        .addProgramClassFileData(getProgramClassFileData())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(getExpectedOutput());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    List<String> seenClassesInBridgeHoisting = new ArrayList<>();
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getProgramClassFileData())
+        .addKeepMainRule(Main.class)
+        .applyIf(
+            !enableBridgeHoistingFromB,
+            testBuilder ->
+                testBuilder.addOptionsModification(
+                    options ->
+                        options.testing.isEligibleForBridgeHoisting =
+                            clazz -> {
+                              String classDescriptor = clazz.getType().toDescriptorString();
+                              seenClassesInBridgeHoisting.add(classDescriptor);
+                              if (classDescriptor.equals(TRANSFORMED_B_DESCRIPTOR)) {
+                                return false;
+                              }
+                              return true;
+                            }))
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              // Inspect A.
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+
+              MethodSubject bridgeOnAMethodSubject = aClassSubject.uniqueMethodWithName("bridge");
+              assertThat(bridgeOnAMethodSubject, onlyIf(enableBridgeHoistingFromB, isPresent()));
+
+              // Inspect B.
+              ClassSubject bClassSubject =
+                  inspector.clazz(DescriptorUtils.descriptorToJavaType(TRANSFORMED_B_DESCRIPTOR));
+              assertThat(bClassSubject, isPresent());
+
+              MethodSubject bridgeOnBMethodSubject = bClassSubject.uniqueMethodWithName("bridge");
+              assertThat(bridgeOnBMethodSubject, notIf(isPresent(), enableBridgeHoistingFromB));
+
+              // Inspect C.
+              ClassSubject cClassSubject = inspector.clazz(C.class);
+              assertThat(cClassSubject, isPresent());
+
+              MethodSubject bridgeOnCMethodSubject = cClassSubject.uniqueMethodWithName("bridge");
+              assertThat(bridgeOnCMethodSubject, isAbsent());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(getExpectedOutput());
+
+    // Verify that the there was the expected calls to isEligibleForBridgeHoisting().
+    if (!enableBridgeHoistingFromB) {
+      assertEquals(
+          Lists.newArrayList(descriptor(C.class), TRANSFORMED_B_DESCRIPTOR),
+          seenClassesInBridgeHoisting);
+    }
+  }
+
+  private List<String> getExpectedOutput() {
+    return ImmutableList.of("A.m()", "C.m()", "C.m()");
+  }
+
+  private List<byte[]> getProgramClassFileData() throws IOException, NoSuchMethodException {
+    return ImmutableList.of(
+        transformer(Main.class)
+            .replaceClassDescriptorInMethodInstructions(
+                descriptor(B.class), TRANSFORMED_B_DESCRIPTOR)
+            .transform(),
+        transformer(A.class).setPublic(A.class.getDeclaredMethod("m")).transform(),
+        transformer(B.class).setClassDescriptor(TRANSFORMED_B_DESCRIPTOR).transform(),
+        transformer(C.class)
+            .setSuper(TRANSFORMED_B_DESCRIPTOR)
+            .replaceClassDescriptorInMethodInstructions(
+                descriptor(B.class), TRANSFORMED_B_DESCRIPTOR)
+            .setBridge(C.class.getDeclaredMethod("bridge"))
+            .transform());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new A().callM();
+      new C().callM();
+      new C().bridge();
+    }
+  }
+
+  @NeverClassInline
+  @NoVerticalClassMerging
+  public static class A {
+
+    @NeverInline
+    /*public*/ void m() {
+      System.out.println("A.m()");
+    }
+
+    @NeverInline
+    public void callM() {
+      m();
+    }
+  }
+
+  @NeverClassInline
+  @NoVerticalClassMerging
+  public static class /*otherpackage.*/ B extends A {}
+
+  @NeverClassInline
+  public static class C extends B {
+
+    @NeverInline
+    void m() {
+      System.out.println("C.m()");
+    }
+
+    @NeverInline
+    public /*bridge*/ void bridge() {
+      this.m();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/invokespecial/InvokeSpecialMethodHandleTest.java b/src/test/java/com/android/tools/r8/cf/methodhandles/invokespecial/InvokeSpecialMethodHandleTest.java
index 7453e9b..10a6837 100644
--- a/src/test/java/com/android/tools/r8/cf/methodhandles/invokespecial/InvokeSpecialMethodHandleTest.java
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/invokespecial/InvokeSpecialMethodHandleTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.apimodel.ApiModelingTestHelper;
 import com.android.tools.r8.errors.UnsupportedFeatureDiagnostic;
 import com.android.tools.r8.transformers.ClassTransformer;
 import com.android.tools.r8.utils.StringUtils;
@@ -75,6 +76,7 @@
         .addProgramClasses(C.class, Main.class)
         .addProgramClassFileData(getTransformedD())
         .setMinApi(parameters.getApiLevel())
+        .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
         .compileWithExpectedDiagnostics(this::checkDiagnostics)
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkResult);
@@ -89,6 +91,7 @@
         .addProgramClassFileData(getTransformedD())
         .setMinApi(parameters.getApiLevel())
         .allowDiagnosticMessages()
+        .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
         .compileWithExpectedDiagnostics(this::checkDiagnostics)
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkResult);
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InitClassToPackagePrivateFieldWithCrossPackageMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InitClassToPackagePrivateFieldWithCrossPackageMergingTest.java
new file mode 100644
index 0000000..ed2130d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InitClassToPackagePrivateFieldWithCrossPackageMergingTest.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2022, 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.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InitClassToPackagePrivateFieldWithCrossPackageMergingTest extends TestBase {
+
+  private static final String NEW_B_DESCRIPTOR = "LB;";
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(A.class, A2.class)
+        .addProgramClassFileData(getProgramClassFileData())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, A2.class)
+        .addProgramClassFileData(getProgramClassFileData())
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertMergedInto(
+                    Reference.classFromClass(A.class),
+                    Reference.classFromDescriptor(NEW_B_DESCRIPTOR)))
+        .enableInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(IllegalAccessError.class);
+  }
+
+  private List<byte[]> getProgramClassFileData() throws Exception {
+    return ImmutableList.of(
+        transformer(Main.class)
+            .replaceClassDescriptorInMethodInstructions(descriptor(B.class), NEW_B_DESCRIPTOR)
+            .transform(),
+        transformer(B.class).setClassDescriptor(NEW_B_DESCRIPTOR).transform());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      A.foo();
+      if (A2.f != 0) {
+        B.bar();
+      }
+    }
+  }
+
+  public static class A {
+
+    @NeverInline
+    public static void foo() {
+      // Will be optimized into reading A2.greeting after inlining, since the body of sayHello() is
+      // empty and A2.greeting will be used to trigger the class initializer of A2.
+      A2.sayHello();
+    }
+  }
+
+  @NoHorizontalClassMerging
+  public static class A2 {
+
+    // Will remain due to the use in Main.main.
+    static int f = (int) System.currentTimeMillis();
+
+    static {
+      System.out.print("Hello");
+    }
+
+    // Will be inlined.
+    static void sayHello() {}
+  }
+
+  public static class /* default package */ B {
+
+    @NeverInline
+    public static void bar() {
+      System.out.println(", world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
index ae53238..0ecf5e3 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -334,6 +334,7 @@
         testForR8(parameters.getBackend())
             .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
             .allowUnusedProguardConfigurationRules()
+            .apply(ApiModelingTestHelper::enableApiCallerIdentification)
             .apply(ApiModelingTestHelper::disableOutlining),
         main,
         programFiles,
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
index 092d2ad..73dbba2 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.compilerapi.androidplatformbuild.AndroidPlatformBuildApiTest;
+import com.android.tools.r8.compilerapi.artprofiles.ArtProfilesForRewritingApiTest;
 import com.android.tools.r8.compilerapi.assertionconfiguration.AssertionConfigurationTest;
 import com.android.tools.r8.compilerapi.classconflictresolver.ClassConflictResolverTest;
 import com.android.tools.r8.compilerapi.desugardependencies.DesugarDependenciesTest;
@@ -53,7 +54,9 @@
 
   private static final List<Class<? extends CompilerApiTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
       ImmutableList.of(
-          StartupProfileApiTest.ApiTest.class, ClassConflictResolverTest.ApiTest.class);
+          ArtProfilesForRewritingApiTest.ApiTest.class,
+          StartupProfileApiTest.ApiTest.class,
+          ClassConflictResolverTest.ApiTest.class);
 
   private final TemporaryFolder temp;
 
diff --git a/src/test/java/com/android/tools/r8/compilerapi/artprofiles/ArtProfilesForRewritingApiTest.java b/src/test/java/com/android/tools/r8/compilerapi/artprofiles/ArtProfilesForRewritingApiTest.java
new file mode 100644
index 0000000..cd7343c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/artprofiles/ArtProfilesForRewritingApiTest.java
@@ -0,0 +1,337 @@
+// Copyright (c) 2022, 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.compilerapi.artprofiles;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TextInputStream;
+import com.android.tools.r8.TextOutputStream;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.compilerapi.artprofiles.ArtProfilesForRewritingApiTest.ApiTest.ArtProfileConsumerForTesting;
+import com.android.tools.r8.compilerapi.mockdata.MockClass;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.profile.art.ArtProfileBuilder;
+import com.android.tools.r8.profile.art.ArtProfileClassRuleInfo;
+import com.android.tools.r8.profile.art.ArtProfileConsumer;
+import com.android.tools.r8.profile.art.ArtProfileMethodRuleInfo;
+import com.android.tools.r8.profile.art.ArtProfileProvider;
+import com.android.tools.r8.profile.art.ArtProfileRuleConsumer;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.ThrowingBiConsumer;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+
+public class ArtProfilesForRewritingApiTest extends CompilerApiTestRunner {
+
+  private static final int SOME_API_LEVEL = 24;
+
+  public ArtProfilesForRewritingApiTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<? extends CompilerApiTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    runTest(test::runD8);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    runTest(test::runR8);
+  }
+
+  private void runTest(
+      ThrowingBiConsumer<ArtProfileConsumerForTesting, ArtProfileConsumerForTesting, Exception>
+          testRunner)
+      throws Exception {
+    ArtProfileConsumerForTesting apiArtProfileConsumer = new ArtProfileConsumerForTesting();
+    ArtProfileConsumerForTesting textualArtProfileConsumer = new ArtProfileConsumerForTesting();
+    testRunner.accept(apiArtProfileConsumer, textualArtProfileConsumer);
+    for (ArtProfileConsumerForTesting artProfileConsumer :
+        new ArtProfileConsumerForTesting[] {apiArtProfileConsumer, textualArtProfileConsumer}) {
+      assertTrue(artProfileConsumer.isFinished());
+      assertTrue(artProfileConsumer.isOutputStreamClosed());
+      assertEquals(ApiTest.textualArtProfileLines, artProfileConsumer.getResidualArtProfileRules());
+      assertEquals(
+          ApiTest.textualArtProfileLines,
+          artProfileConsumer.getResidualArtProfileRulesFromOutputStream());
+    }
+  }
+
+  public static class ApiTest extends CompilerApiTest {
+
+    static ClassReference mockClassReference = Reference.classFromClass(MockClass.class);
+    static MethodReference mockInitMethodReference =
+        Reference.method(mockClassReference, "<init>", Collections.emptyList(), null);
+    static MethodReference mockMainMethodReference =
+        Reference.method(
+            mockClassReference,
+            "main",
+            Collections.singletonList(Reference.classFromClass(String[].class)),
+            null);
+    static List<String> textualArtProfileLines =
+        Arrays.asList(
+            "Lcom/android/tools/r8/compilerapi/mockdata/MockClass;",
+            "PLcom/android/tools/r8/compilerapi/mockdata/MockClass;-><init>()V",
+            "HSPLcom/android/tools/r8/compilerapi/mockdata/MockClass;->main([Ljava/lang/String;)V");
+
+    public ApiTest(Object parameters) {
+      super(parameters);
+    }
+
+    public void runD8(
+        ArtProfileConsumerForTesting apiArtProfileConsumer,
+        ArtProfileConsumerForTesting textualArtProfileConsumer)
+        throws Exception {
+      ApiArtProfileProviderForTesting apiArtProfileProvider = new ApiArtProfileProviderForTesting();
+      TextualArtProfileProviderForTesting textualArtProfileProvider =
+          new TextualArtProfileProviderForTesting();
+      D8Command.Builder commandBuilder =
+          D8Command.builder()
+              .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown())
+              .addLibraryFiles(getJava8RuntimeJar())
+              .setMinApiLevel(SOME_API_LEVEL)
+              .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+              .addArtProfileForRewriting(apiArtProfileProvider, apiArtProfileConsumer)
+              .addArtProfileForRewriting(textualArtProfileProvider, textualArtProfileConsumer);
+      D8.run(commandBuilder.build());
+      assertTrue(textualArtProfileProvider.inputStream.isClosed());
+    }
+
+    public void runR8(
+        ArtProfileConsumerForTesting apiArtProfileConsumer,
+        ArtProfileConsumerForTesting textualArtProfileConsumer)
+        throws Exception {
+      ApiArtProfileProviderForTesting apiArtProfileProvider = new ApiArtProfileProviderForTesting();
+      TextualArtProfileProviderForTesting textualArtProfileProvider =
+          new TextualArtProfileProviderForTesting();
+      R8Command.Builder commandBuilder =
+          R8Command.builder()
+              .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown())
+              .addProguardConfiguration(
+                  Collections.singletonList("-keep class * { *; }"), Origin.unknown())
+              .addLibraryFiles(getJava8RuntimeJar())
+              .setMinApiLevel(SOME_API_LEVEL)
+              .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+              .addArtProfileForRewriting(apiArtProfileProvider, apiArtProfileConsumer)
+              .addArtProfileForRewriting(textualArtProfileProvider, textualArtProfileConsumer);
+      R8.run(commandBuilder.build());
+      assertTrue(textualArtProfileProvider.inputStream.isClosed());
+    }
+
+    @Test
+    public void testD8() throws Exception {
+      runD8(null, null);
+    }
+
+    @Test
+    public void testR8() throws Exception {
+      runR8(null, null);
+    }
+
+    static class ApiArtProfileProviderForTesting implements ArtProfileProvider {
+
+      @Override
+      public void getArtProfile(ArtProfileBuilder profileBuilder) {
+        profileBuilder
+            .addClassRule(
+                classRuleBuilder -> classRuleBuilder.setClassReference(mockClassReference))
+            .addMethodRule(
+                methodRuleBuilder ->
+                    methodRuleBuilder
+                        .setMethodReference(mockInitMethodReference)
+                        .setMethodRuleInfo(
+                            methodRuleInfoBuilder -> methodRuleInfoBuilder.setIsHot(true))
+                        .setMethodRuleInfo(
+                            methodRuleInfoBuilder -> methodRuleInfoBuilder.setIsPostStartup(true)))
+            .addMethodRule(
+                methodRuleBuilder ->
+                    methodRuleBuilder
+                        .setMethodReference(mockMainMethodReference)
+                        .setMethodRuleInfo(
+                            methodRuleInfoBuilder ->
+                                methodRuleInfoBuilder
+                                    .setIsHot(true)
+                                    .setIsStartup(true)
+                                    .setIsPostStartup(true)));
+      }
+
+      @Override
+      public Origin getOrigin() {
+        return Origin.unknown();
+      }
+    }
+
+    static class TextualArtProfileProviderForTesting implements ArtProfileProvider {
+
+      final ClosableByteArrayInputStream inputStream =
+          new ClosableByteArrayInputStream(
+              String.join("\n", textualArtProfileLines).concat("\n").getBytes());
+
+      @Override
+      public void getArtProfile(ArtProfileBuilder profileBuilder) {
+        profileBuilder.addHumanReadableArtProfile(
+            new TextInputStream() {
+              @Override
+              public InputStream getInputStream() {
+                return inputStream;
+              }
+
+              @Override
+              public Charset getCharset() {
+                return StandardCharsets.UTF_8;
+              }
+            },
+            parserBuilderConsumer -> {});
+      }
+
+      @Override
+      public Origin getOrigin() {
+        return Origin.unknown();
+      }
+    }
+
+    static class ArtProfileConsumerForTesting implements ArtProfileConsumer {
+
+      private boolean finished;
+      private final List<String> residualArtProfileRules = new ArrayList<>();
+      private final ClosableByteArrayOutputStream outputStream =
+          new ClosableByteArrayOutputStream();
+
+      @Override
+      public TextOutputStream getHumanReadableArtProfileConsumer() {
+        return new TextOutputStream() {
+
+          @Override
+          public OutputStream getOutputStream() {
+            return outputStream;
+          }
+
+          @Override
+          public Charset getCharset() {
+            return StandardCharsets.UTF_8;
+          }
+        };
+      }
+
+      @Override
+      public ArtProfileRuleConsumer getRuleConsumer() {
+        return new ArtProfileRuleConsumer() {
+
+          @Override
+          public void acceptClassRule(
+              ClassReference classReference, ArtProfileClassRuleInfo classRuleInfo) {
+            residualArtProfileRules.add(classReference.getDescriptor());
+          }
+
+          @Override
+          public void acceptMethodRule(
+              MethodReference methodReference, ArtProfileMethodRuleInfo methodRuleInfo) {
+            StringBuilder builder = new StringBuilder();
+            if (methodRuleInfo.isHot()) {
+              builder.append('H');
+            }
+            if (methodRuleInfo.isStartup()) {
+              builder.append('S');
+            }
+            if (methodRuleInfo.isPostStartup()) {
+              builder.append('P');
+            }
+            residualArtProfileRules.add(
+                builder
+                    .append(methodReference.getHolderClass().getDescriptor())
+                    .append("->")
+                    .append(methodReference.getMethodName())
+                    .append(methodReference.getMethodDescriptor())
+                    .toString());
+          }
+        };
+      }
+
+      @Override
+      public void finished(DiagnosticsHandler handler) {
+        finished = true;
+      }
+
+      List<String> getResidualArtProfileRulesFromOutputStream()
+          throws UnsupportedEncodingException {
+        return Arrays.asList(outputStream.toString(StandardCharsets.UTF_8.name()).split("\n"));
+      }
+
+      List<String> getResidualArtProfileRules() {
+        return residualArtProfileRules;
+      }
+
+      boolean isFinished() {
+        return finished;
+      }
+
+      boolean isOutputStreamClosed() {
+        return outputStream.isClosed();
+      }
+    }
+
+    private static class ClosableByteArrayInputStream extends ByteArrayInputStream {
+
+      private boolean closed;
+
+      public ClosableByteArrayInputStream(byte[] buf) {
+        super(buf);
+      }
+
+      @Override
+      public void close() throws IOException {
+        super.close();
+        closed = true;
+      }
+
+      public boolean isClosed() {
+        return closed;
+      }
+    }
+
+    private static class ClosableByteArrayOutputStream extends ByteArrayOutputStream {
+
+      private boolean closed;
+
+      @Override
+      public void close() throws IOException {
+        super.close();
+        closed = true;
+      }
+
+      public boolean isClosed() {
+        return closed;
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/CfDebugTestConfig.java b/src/test/java/com/android/tools/r8/debug/CfDebugTestConfig.java
index e8fa240..0a6acf6 100644
--- a/src/test/java/com/android/tools/r8/debug/CfDebugTestConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/CfDebugTestConfig.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.debug;
 
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.nio.file.Path;
@@ -15,6 +17,8 @@
 
   public static final Path JDWP_JAR = ToolHelper.getJdwpTestsCfJarPath(AndroidApiLevel.N);
 
+  private final CfRuntime runtime;
+
   public CfDebugTestConfig() {
     this(Collections.emptyList());
   }
@@ -23,13 +27,19 @@
     this(Arrays.asList(paths));
   }
 
+  @Deprecated
   public CfDebugTestConfig(List<Path> paths) {
+    this(TestRuntime.getDefaultCfRuntime(), paths);
+  }
+
+  public CfDebugTestConfig(CfRuntime runtime, List<Path> paths) {
+    this.runtime = runtime;
     addPaths(JDWP_JAR);
     addPaths(paths);
   }
 
   @Override
-  public final RuntimeKind getRuntimeKind() {
-    return RuntimeKind.CF;
+  public final CfRuntime getRuntime() {
+    return runtime;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/debug/ClassInitializationTest.java b/src/test/java/com/android/tools/r8/debug/ClassInitializationTest.java
deleted file mode 100644
index 8931c9d..0000000
--- a/src/test/java/com/android/tools/r8/debug/ClassInitializationTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.debug;
-
-import org.apache.harmony.jpda.tests.framework.jdwp.Value;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-public class ClassInitializationTest extends DebugTestBase {
-
-  private static DebugTestConfig config;
-
-  @BeforeClass
-  public static void setup() {
-    config = new D8DebugTestResourcesConfig(temp);
-  }
-
-  @Test
-  public void testStaticAssingmentInitialization() throws Throwable {
-    final String SOURCE_FILE = "ClassInitializerAssignmentInitialization.java";
-    final String CLASS = "ClassInitializerAssignmentInitialization";
-
-    runDebugTest(
-        config,
-        CLASS,
-        breakpoint(CLASS, "<clinit>"),
-        run(),
-        checkLine(SOURCE_FILE, 7),
-        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(0)),
-        checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
-        checkStaticFieldClinitSafe(CLASS, "z", null, Value.createInt(0)),
-        stepOver(),
-        checkLine(SOURCE_FILE, 10),
-        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(1)),
-        checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
-        checkStaticFieldClinitSafe(CLASS, "z", null, Value.createInt(0)),
-        breakpoint(CLASS, "main"),
-        run(),
-        checkStaticField(CLASS, "x", null, Value.createInt(1)),
-        checkStaticField(CLASS, "y", null, Value.createInt(0)),
-        checkStaticField(CLASS, "z", null, Value.createInt(2)),
-        run());
-  }
-
-  @Test
-  public void testBreakpointInEmptyClassInitializer() throws Throwable {
-    final String SOURCE_FILE = "ClassInitializerEmpty.java";
-    final String CLASS = "ClassInitializerEmpty";
-
-    runDebugTest(
-        config,
-        CLASS,
-        breakpoint(CLASS, "<clinit>"),
-        run(),
-        checkLine(SOURCE_FILE, 8),
-        run());
-  }
-
-  @Test
-  public void testStaticBlockInitialization() throws Throwable {
-    final String SOURCE_FILE = "ClassInitializerStaticBlockInitialization.java";
-    final String CLASS = "ClassInitializerStaticBlockInitialization";
-
-    runDebugTest(
-        config,
-        CLASS,
-        breakpoint(CLASS, "<clinit>"),
-        run(),
-        checkLine(SOURCE_FILE, 12),
-        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(0)),
-        stepOver(),
-        checkLine(SOURCE_FILE, 13),
-        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(1)),
-        stepOver(),
-        checkLine(SOURCE_FILE, 14),
-        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
-        stepOver(),
-        checkLine(SOURCE_FILE, 17),
-        stepOver(),
-        checkLine(SOURCE_FILE, 19),
-        breakpoint(CLASS, "main"),
-        run(),
-        checkLine(SOURCE_FILE, 23),
-        checkStaticField(CLASS, "x", null, Value.createInt(3)),
-        run());
-  }
-
-  @Test
-  public void testStaticMixedInitialization() throws Throwable {
-    final String SOURCE_FILE = "ClassInitializerMixedInitialization.java";
-    final String CLASS = "ClassInitializerMixedInitialization";
-
-    runDebugTest(
-        config,
-        CLASS,
-        breakpoint(CLASS, "<clinit>"),
-        run(),
-        checkLine(SOURCE_FILE, 8),
-        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(0)),
-        checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
-        stepOver(),
-        checkLine(SOURCE_FILE, 12),
-        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(1)),
-        checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
-        stepOver(),
-        checkLine(SOURCE_FILE, 13),
-        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
-        checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
-        stepOver(),
-        checkLine(SOURCE_FILE, 16),
-        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
-        checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
-        stepOver(),
-        checkLine(SOURCE_FILE, 18),
-        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
-        checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(2)),
-        breakpoint(CLASS, "main"),
-        run(),
-        checkLine(SOURCE_FILE, 22),
-        checkStaticField(CLASS, "x", null, Value.createInt(3)),
-        checkStaticField(CLASS, "y", null, Value.createInt(2)),
-        run());
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/debug/ContinuousKotlinSteppingTest.java b/src/test/java/com/android/tools/r8/debug/ContinuousKotlinSteppingTest.java
index c3e45b6..2a8e7c0 100644
--- a/src/test/java/com/android/tools/r8/debug/ContinuousKotlinSteppingTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ContinuousKotlinSteppingTest.java
@@ -36,14 +36,16 @@
   @Test
   public void testContinuousSingleStepKotlinApp() throws Throwable {
     KotlinDebugD8Config d8Config =
-        KotlinDebugD8Config.build(kotlinParameters, parameters.getApiLevel());
+        KotlinDebugD8Config.build(
+            kotlinParameters, parameters.getApiLevel(), parameters.getRuntime().asDex());
     runContinuousTest("KotlinApp", d8Config, MAIN_METHOD_NAME);
   }
 
   @Test
   public void testContinuousSingleStepKotlinInline() throws Throwable {
     KotlinDebugD8Config d8Config =
-        KotlinDebugD8Config.build(kotlinParameters, parameters.getApiLevel());
+        KotlinDebugD8Config.build(
+            kotlinParameters, parameters.getApiLevel(), parameters.getRuntime().asDex());
     runContinuousTest("KotlinInline", d8Config, MAIN_METHOD_NAME);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/debug/D8DebugTestConfig.java b/src/test/java/com/android/tools/r8/debug/D8DebugTestConfig.java
index ef2205a..013d166 100644
--- a/src/test/java/com/android/tools/r8/debug/D8DebugTestConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/D8DebugTestConfig.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.D8Command;
 import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.TestRuntime.DexRuntime;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
@@ -13,6 +14,7 @@
 import com.android.tools.r8.utils.ListUtils;
 import java.nio.file.Path;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.function.Consumer;
 import org.junit.rules.TemporaryFolder;
@@ -20,6 +22,13 @@
 /** Test configuration with utilities for compiling with D8 and adding results to the classpath. */
 public class D8DebugTestConfig extends DexDebugTestConfig {
 
+  @Deprecated
+  public D8DebugTestConfig() {}
+
+  public D8DebugTestConfig(DexRuntime runtime) {
+    super(runtime, Collections.emptyList());
+  }
+
   // Use the option with api-level below.
   @Deprecated()
   public static AndroidApp d8Compile(List<Path> paths, Consumer<InternalOptions> optionsConsumer) {
diff --git a/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java b/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
index 04477bd..426da42 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
@@ -7,9 +7,9 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
 import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.FrameInspector;
-import com.android.tools.r8.debug.DebugTestConfig.RuntimeKind;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.StringUtils;
@@ -289,10 +289,10 @@
     String sig = reference.getMethodSignature();
     List<Variable> variables = reference.getVisibleVariables();
     int frameDepth = reference.getFrameDepth();
-    RuntimeKind referenceRuntime = reference.getConfig().getRuntimeKind();
+    TestRuntime referenceRuntime = reference.getConfig().getRuntime();
     for (int i = 1; i < states.size(); i++) {
       DebuggeeState state = states.get(i);
-      RuntimeKind stateRuntime = state.getConfig().getRuntimeKind();
+      TestRuntime stateRuntime = state.getConfig().getRuntime();
       if (verifyFiles) {
         assertEquals("source file mismatch", file, state.getSourceFile());
       }
@@ -328,12 +328,12 @@
     }
   }
 
-  private static boolean shouldIgnoreVariable(Variable variable, RuntimeKind runtime) {
-    return runtime == RuntimeKind.DEX && variable.getName().isEmpty();
+  private static boolean shouldIgnoreVariable(Variable variable, TestRuntime runtime) {
+    return runtime.isDex() && variable.getName().isEmpty();
   }
 
   private static void verifyVariablesEqual(
-      RuntimeKind xRuntime, List<Variable> xs, RuntimeKind yRuntime, List<Variable> ys) {
+      TestRuntime xRuntime, List<Variable> xs, TestRuntime yRuntime, List<Variable> ys) {
     Map<String, Variable> map = new HashMap<>(xs.size());
     for (Variable x : xs) {
       if (!shouldIgnoreVariable(x, xRuntime)) {
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 59d408f..c0f7ea3 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -4,9 +4,9 @@
 package com.android.tools.r8.debug;
 
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
-import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command.NopCommand;
 import com.android.tools.r8.errors.Unreachable;
@@ -189,20 +189,18 @@
   protected DebugTestRunner getDebugTestRunner(
       DebugTestConfig config, String debuggeeClass, List<JUnit3Wrapper.Command> commands)
       throws Throwable {
-    // TODO(b/199700280): Reenable on 12.0.0 when we have the libjdwp.so file include and the flags
-    // fixed.
     Assume.assumeTrue(
-        "Skipping test " + testName.getMethodName() + " because debugging not enabled in 12.0.0",
-        !ToolHelper.getDexVm().isEqualTo(DexVm.ART_12_0_0_HOST));
-    // Skip test due to unsupported runtime.
-    Assume.assumeTrue("Skipping test " + testName.getMethodName() + " because ART is not supported",
-        ToolHelper.artSupported());
-    Assume.assumeTrue("Skipping test " + testName.getMethodName()
+        "Skipping test "
+            + testName.getMethodName()
             + " because debug tests are not yet supported on Windows",
         !ToolHelper.isWindows());
-    Assume.assumeTrue("Skipping test " + testName.getMethodName()
-            + " because debug tests are not yet supported on device",
-        ToolHelper.getDexVm().getKind() == ToolHelper.DexVm.Kind.HOST);
+
+    // TODO(b/199700280): Reenable on 12.0.0 when we have the libjdwp.so file include and the flags
+    //  fixed.
+    Assume.assumeTrue(
+        "Skipping test " + testName.getMethodName() + " because debugging not enabled in 12.0.0",
+        config.getRuntime().isCf()
+            || !config.getRuntime().asDex().getVersion().isEqualTo(Version.V12_0_0));
 
     ClassNameMapper classNameMapper =
         config.getProguardMap() == null
@@ -545,14 +543,7 @@
 
   protected final JUnit3Wrapper.Command checkStaticFieldClinitSafe(
       String className, String fieldName, String fieldSignature, Value expectedValue) {
-    return inspect(t -> {
-      // TODO(65148874): The current Art from AOSP master hangs when requesting static fields
-      // when breaking in <clinit>. Last known good version is 7.0.0.
-      Assume.assumeTrue(
-          "Skipping test " + testName.getMethodName() + " because ART version is not supported",
-          t.isCfRuntime() || ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V7_0_0));
-      checkStaticField(className, fieldName, fieldSignature, expectedValue);
-    });
+    return inspect(t -> checkStaticField(className, fieldName, fieldSignature, expectedValue));
   }
 
   protected final JUnit3Wrapper.Command checkStaticField(
@@ -945,18 +936,27 @@
           try {
             command.perform(this);
           } catch (TestErrorException e) {
-            boolean ignoreException = false;
-            if (config.isDexRuntime()
-                && ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)) {
-              // Dalvik has flaky synchronization issue on shutdown. The workaround is to ignore
-              // the exception if and only if we know that it's the final resume command.
-              if (debuggeeState == null && commandsQueue.isEmpty()) {
-                // We should receive the VMDeath event and transition to the Exit state here.
-                processEvents();
-                assert state == State.Exit;
-                ignoreException = true;
-              }
-            }
+              boolean ignoreException =
+                  config
+                      .getRuntime()
+                      .match(
+                          cfRuntime -> false,
+                          (dexRuntime, version) -> {
+                            if (version.isOlderThanOrEqual(Version.V4_4_4)) {
+                              // Dalvik has flaky synchronization issue on shutdown. The workaround
+                              // is to ignore
+                              // the exception if and only if we know that it's the final resume
+                              // command.
+                              if (debuggeeState == null && commandsQueue.isEmpty()) {
+                                // We should receive the VMDeath event and transition to the Exit
+                                // state here.
+                                processEvents();
+                                assert state == State.Exit;
+                                return true;
+                              }
+                            }
+                            return false;
+                          });
             if (!ignoreException) {
               throw e;
             }
@@ -1072,20 +1072,34 @@
 
         ArtTestOptions(String[] debuggeePath) {
           // Set debuggee command-line.
-          if (config.isDexRuntime()) {
-            ArtCommandBuilder artCommandBuilder = new ArtCommandBuilder(ToolHelper.getDexVm());
-            if (ToolHelper.getDexVm().getVersion().isNewerThan(DexVm.Version.V5_1_1)) {
-              artCommandBuilder.appendArtOption("-Xcompiler-option");
-              artCommandBuilder.appendArtOption("--debuggable");
-            }
-            if (ToolHelper.getDexVm().getVersion().isNewerThanOrEqual(DexVm.Version.V9_0_0)) {
-              artCommandBuilder.appendArtOption("-XjdwpProvider:internal");
-            }
-            if (DEBUG_TESTS && ToolHelper.getDexVm().getVersion().isNewerThan(Version.V4_4_4)) {
-              artCommandBuilder.appendArtOption("-verbose:jdwp");
-            }
-            setProperty("jpda.settings.debuggeeJavaPath", artCommandBuilder.build());
-          }
+          config
+              .getRuntime()
+              .match(
+                  cfRuntime -> {
+                    setProperty(
+                        "jpda.settings.debuggeeJavaPath", cfRuntime.getJavaExecutable().toString());
+                  },
+                  (dexRuntime, version) -> {
+                    ArtCommandBuilder artCommandBuilder = new ArtCommandBuilder(dexRuntime.getVm());
+                    if (version.isNewerThan(Version.V5_1_1)) {
+                      artCommandBuilder.appendArtOption("-Xcompiler-option");
+                      artCommandBuilder.appendArtOption("--debuggable");
+                    }
+                    if (version.isNewerThanOrEqual(Version.V13_0_0)) {
+                      // TODO(b/199700280): These options may be the same for V12 once the libs are
+                      // added.
+                      artCommandBuilder.appendArtOption("-Xplugin:libopenjdkjvmti.so");
+                      setProperty("jpda.settings.debuggeeAgentArgument", "-agentpath:");
+                      setProperty("jpda.settings.debuggeeAgentName", "libjdwp.so");
+                    } else if (version.isNewerThanOrEqual(Version.V9_0_0)) {
+                      artCommandBuilder.appendArtOption("-XjdwpProvider:internal");
+                    }
+                    if (DEBUG_TESTS && version.isNewerThan(Version.V4_4_4)) {
+                      artCommandBuilder.appendArtOption("-verbose:jdwp");
+                    }
+                    String build = artCommandBuilder.build();
+                    setProperty("jpda.settings.debuggeeJavaPath", build);
+                  });
 
           // Set debuggee classpath
           String debuggeeClassPath = String.join(File.pathSeparator, debuggeePath);
@@ -1442,6 +1456,10 @@
         return config;
       }
 
+      public TestRuntime getRuntime() {
+        return config.getRuntime();
+      }
+
       public boolean isCfRuntime() {
         return getConfig().isCfRuntime();
       }
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java b/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
index 4ab22aa..83e7c3f 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
@@ -5,6 +5,8 @@
 
 import static com.android.tools.r8.naming.ClassNameMapper.MissingFileAction.MISSING_FILE_IS_ERROR;
 
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -20,6 +22,16 @@
     DEX
   }
 
+  public static DebugTestConfig create(TestRuntime runtime, Path... paths) {
+    if (runtime.isCf()) {
+      return new CfDebugTestConfig(runtime.asCf(), Arrays.asList(paths));
+    }
+    if (runtime.isDex()) {
+      return new DexDebugTestConfig(runtime.asDex(), Arrays.asList(paths));
+    }
+    throw new Unreachable();
+  }
+
   private boolean mustProcessAllCommands = true;
   private List<Path> paths = new ArrayList<>();
 
@@ -27,15 +39,15 @@
   private ClassNameMapper.MissingFileAction missingProguardMapAction;
   private boolean usePcForMissingLineNumberTable = false;
 
-  /** The expected runtime kind for the debuggee. */
-  public abstract RuntimeKind getRuntimeKind();
+  /** The runtime to use for the debuggee. */
+  public abstract TestRuntime getRuntime();
 
   public boolean isCfRuntime() {
-    return getRuntimeKind() == RuntimeKind.CF;
+    return getRuntime().isCf();
   }
 
   public boolean isDexRuntime() {
-    return getRuntimeKind() == RuntimeKind.DEX;
+    return getRuntime().isDex();
   }
 
   public void allowUsingPcForMissingLineNumberTable() {
@@ -95,7 +107,7 @@
         new StringBuilder()
             .append("DebugTestConfig{")
             .append("runtime:")
-            .append(getRuntimeKind())
+            .append(getRuntime())
             .append(", classpath:[")
             .append(
                 String.join(", ", paths.stream().map(Path::toString).collect(Collectors.toList())))
diff --git a/src/test/java/com/android/tools/r8/debug/DexDebugTestConfig.java b/src/test/java/com/android/tools/r8/debug/DexDebugTestConfig.java
index babf170..c74c84a 100644
--- a/src/test/java/com/android/tools/r8/debug/DexDebugTestConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/DexDebugTestConfig.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.debug;
 
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestRuntime.DexRuntime;
 import com.android.tools.r8.ToolHelper;
 import java.nio.file.Path;
 import java.util.Arrays;
@@ -12,24 +14,31 @@
 /** Base test configuration with DEX version of JDWP. */
 public class DexDebugTestConfig extends DebugTestConfig {
 
-  public static final Path JDWP_DEX_JAR =
-      ToolHelper.getJdwpTestsDexJarPath(ToolHelper.getMinApiLevelForDexVm());
+  private final DexRuntime runtime;
 
+  @Deprecated
   public DexDebugTestConfig() {
     this(Collections.emptyList());
   }
 
+  @Deprecated
   public DexDebugTestConfig(Path... paths) {
     this(Arrays.asList(paths));
   }
 
+  @Deprecated
   public DexDebugTestConfig(List<Path> paths) {
-    addPaths(JDWP_DEX_JAR);
+    this(new DexRuntime(ToolHelper.getDexVm()), paths);
+  }
+
+  public DexDebugTestConfig(TestRuntime.DexRuntime runtime, List<Path> paths) {
+    this.runtime = runtime;
+    addPaths(ToolHelper.getJdwpTestsDexJarPath(runtime.maxSupportedApiLevel()));
     addPaths(paths);
   }
 
   @Override
-  public final RuntimeKind getRuntimeKind() {
-    return RuntimeKind.DEX;
+  public TestRuntime getRuntime() {
+    return runtime;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java b/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java
index 927b5ba..e603d08 100644
--- a/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java
@@ -7,7 +7,9 @@
 import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringForTesting.getCompanionClassNameSuffix;
 import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringForTesting.getDefaultMethodPrefix;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
 import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringForTesting;
 import java.nio.file.Path;
@@ -42,6 +44,10 @@
 
   @Test
   public void testDefaultMethod() throws Throwable {
+    // TODO(b/244683447): This test fails on ART 13 when checking current method in doSomething.
+    assumeTrue(
+        config.getRuntime().isCf()
+            || !config.getRuntime().asDex().getVersion().isEqualTo(Version.V13_0_0));
     String debuggeeClass = "DebugInterfaceMethod";
     String parameterName = "msg";
     String localVariableName = "name";
diff --git a/src/test/java/com/android/tools/r8/debug/JvmSyntheticTestRunner.java b/src/test/java/com/android/tools/r8/debug/JvmSyntheticTestRunner.java
index e3f0660..0600584 100644
--- a/src/test/java/com/android/tools/r8/debug/JvmSyntheticTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/JvmSyntheticTestRunner.java
@@ -3,13 +3,10 @@
 import static com.android.tools.r8.references.Reference.methodFromMethod;
 import static org.hamcrest.CoreMatchers.containsString;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.debug.JvmSyntheticTest.A;
 import com.android.tools.r8.debug.JvmSyntheticTest.Runner;
-import java.io.IOException;
-import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -32,17 +29,17 @@
   }
 
   @Test
-  public void testStacktrace() throws ExecutionException, CompilationFailedException, IOException {
+  public void testStacktrace() throws Throwable {
     testForJvm()
         .addProgramClasses(A.class, CLASS)
         .run(parameters.getRuntime(), CLASS)
         .assertSuccessWithOutputThatMatches(
-            containsString("at com.android.tools.r8.debug.JvmSyntheticTest$A.access$000"));
+            containsString("at com.android.tools.r8.debug.JvmSyntheticTest$A.access$000"))
+        .debugger(this::testDebug)
+        .debugger(this::testDebugIntelliJ);
   }
 
-  @Test
-  public void testDebug() throws Throwable {
-    DebugTestConfig debugTestConfig = testForJvm().addProgramClasses(A.class, CLASS).debugConfig();
+  public void testDebug(DebugTestConfig debugTestConfig) throws Throwable {
     runDebugTest(
         debugTestConfig,
         CLASS,
@@ -54,9 +51,7 @@
         run());
   }
 
-  @Test
-  public void testDebugIntelliJ() throws Throwable {
-    DebugTestConfig debugTestConfig = testForJvm().addProgramClasses(A.class, CLASS).debugConfig();
+  public void testDebugIntelliJ(DebugTestConfig debugTestConfig) throws Throwable {
     runDebugTest(
         debugTestConfig,
         CLASS,
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinDebugD8Config.java b/src/test/java/com/android/tools/r8/debug/KotlinDebugD8Config.java
index 348d43a..4166711 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinDebugD8Config.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinDebugD8Config.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestRuntime.DexRuntime;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.io.IOException;
 import java.nio.file.Path;
@@ -22,6 +23,10 @@
 /** Shared test configuration for D8 compiled resources from the "kotlinR8TestResources/debug". */
 class KotlinDebugD8Config extends D8DebugTestConfig {
 
+  public KotlinDebugD8Config(DexRuntime runtime) {
+    super(runtime);
+  }
+
   static final KotlinCompileMemoizer compiledKotlinJars =
       getCompileMemoizer(KotlinTestBase.getKotlinFilesInResource("debug"))
           .configure(KotlinCompilerTool::includeRuntime);
@@ -40,9 +45,9 @@
   }
 
   public static KotlinDebugD8Config build(
-      KotlinTestParameters kotlinTestParameters, AndroidApiLevel apiLevel) {
+      KotlinTestParameters kotlinTestParameters, AndroidApiLevel apiLevel, DexRuntime runtime) {
     try {
-      KotlinDebugD8Config kotlinDebugD8Config = new KotlinDebugD8Config();
+      KotlinDebugD8Config kotlinDebugD8Config = new KotlinDebugD8Config(runtime);
       kotlinDebugD8Config.addPaths(compiledResourcesMemoized.apply(kotlinTestParameters, apiLevel));
       return kotlinDebugD8Config;
     } catch (Throwable e) {
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java b/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
index 419a379..901e412 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
@@ -5,10 +5,12 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -41,7 +43,8 @@
   }
 
   protected KotlinDebugD8Config getD8Config() {
-    return KotlinDebugD8Config.build(kotlinParameters, parameters.getApiLevel());
+    return KotlinDebugD8Config.build(
+        kotlinParameters, parameters.getApiLevel(), parameters.getRuntime().asDex());
   }
 
   @Test
@@ -254,6 +257,9 @@
 
   @Test
   public void testNestedInlining() throws Throwable {
+    assumeTrue(
+        "b/244704042: Incorrect step-into StringBuilder.",
+        parameters.isCfRuntime() || !parameters.getDexRuntimeVersion().isEqualTo(Version.V13_0_0));
     // Count the number of lines in the source file. This is needed to check that inlined code
     // refers to non-existing line numbers.
     Path sourceFilePath =
@@ -336,7 +342,7 @@
         checkLocals(left_mangledLvName, right_mangledLvName),
         // Enter "foo"
         stepInto(),
-        // TODO(b/207743106): Remove when resolved.
+        // See b/207743106 for incorrect debug info on Kotlin 1.6.
         applyIf(
             kotlinParameters.getCompilerVersion() == KotlinCompilerVersion.KOTLINC_1_6_0,
             this::stepInto),
@@ -395,7 +401,7 @@
         checkNoLocal(inlinee2_lambda2_inlineScope),
         // Enter the call to "foo"
         stepInto(),
-        // TODO(b/207743106): Remove when resolved.
+        // See b/207743106 for incorrect debug info on Kotlin 1.6.
         applyIf(
             kotlinParameters.getCompilerVersion() == KotlinCompilerVersion.KOTLINC_1_6_0,
             this::stepInto),
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinTest.java b/src/test/java/com/android/tools/r8/debug/KotlinTest.java
index 4c9f8c0..a7ae2b0 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinTest.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinTest.java
@@ -33,7 +33,8 @@
   }
 
   protected KotlinDebugD8Config getD8Config() {
-    return KotlinDebugD8Config.build(kotlinParameters, parameters.getApiLevel());
+    return KotlinDebugD8Config.build(
+        kotlinParameters, parameters.getApiLevel(), parameters.getRuntime().asDex());
   }
 
   // TODO(shertz) simplify test
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
index 0e77b85..1635252 100644
--- a/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
@@ -4,46 +4,67 @@
 package com.android.tools.r8.debug;
 
 import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.D8TestCompileResult;
-import com.android.tools.r8.JvmTestBuilder;
-import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.debug.LambdaOuterContextTest.Converter;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import org.apache.harmony.jpda.tests.framework.jdwp.Value;
 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 LambdaOuterContextTestRunner extends DebugTestBase {
 
   public static final Class<?> CLASS = LambdaOuterContextTest.class;
   public static final String EXPECTED = StringUtils.lines("84");
 
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().build();
+  }
+
+  private final TestParameters parameters;
+
+  public LambdaOuterContextTestRunner(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
   @Test
   public void testJvm() throws Throwable {
-    JvmTestBuilder jvmTestBuilder = testForJvm().addTestClasspath();
-    jvmTestBuilder.run(CLASS).assertSuccessWithOutput(EXPECTED);
-    runDebugger(jvmTestBuilder.debugConfig());
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClassesAndInnerClasses(CLASS)
+        .run(parameters.getRuntime(), CLASS)
+        .assertSuccessWithOutput(EXPECTED)
+        .debugger(this::runDebugger);
   }
 
   @Test
   public void testD8() throws Throwable {
-    D8TestCompileResult compileResult =
-        testForD8().addProgramClassesAndInnerClasses(CLASS).compile();
-    compileResult.run(CLASS).assertSuccessWithOutput(EXPECTED);
-    runDebugger(compileResult.debugConfig());
+    testForD8(parameters.getBackend())
+        .addProgramClassesAndInnerClasses(CLASS)
+        .setMinApi(AndroidApiLevel.B)
+        .run(parameters.getRuntime(), CLASS)
+        .assertSuccessWithOutput(EXPECTED)
+        .debugger(this::runDebugger);
   }
 
   @Test
-  public void testR8Cf() throws Throwable {
-    R8TestCompileResult compileResult =
-        testForR8(Backend.CF)
-            .addProgramClassesAndInnerClasses(CLASS)
-            .debug()
-            .addDontObfuscate()
-            .noTreeShaking()
-            .compile();
-    compileResult.run(CLASS).assertSuccessWithOutput(EXPECTED);
-    runDebugger(compileResult.debugConfig());
+  public void testR8() throws Throwable {
+    testForR8(parameters.getBackend())
+        .addProgramClassesAndInnerClasses(CLASS)
+        .debug()
+        .addDontObfuscate()
+        .noTreeShaking()
+        .setMinApi(AndroidApiLevel.B)
+        .run(parameters.getRuntime(), CLASS)
+        .assertSuccessWithOutput(EXPECTED)
+        .debugger(this::runDebugger);
   }
 
   private void runDebugger(DebugTestConfig config) throws Throwable {
diff --git a/src/test/java/com/android/tools/r8/debug/LoadInvokeLoadOptimizationTestRunner.java b/src/test/java/com/android/tools/r8/debug/LoadInvokeLoadOptimizationTestRunner.java
index e382d39..9cd5079 100644
--- a/src/test/java/com/android/tools/r8/debug/LoadInvokeLoadOptimizationTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/LoadInvokeLoadOptimizationTestRunner.java
@@ -3,11 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.debug;
 
-import com.android.tools.r8.ToolHelper;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.FrameInspector;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DescriptorUtils;
-import java.util.List;
 import org.apache.harmony.jpda.tests.framework.jdwp.Value;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -19,44 +20,56 @@
 
   static final Class CLASS = LoadInvokeLoadOptimizationTest.class;
   static final String NAME = CLASS.getCanonicalName();
-  static final String DESC = DescriptorUtils.javaTypeToDescriptor(NAME);
   static final String FILE = CLASS.getSimpleName() + ".java";
   static final AndroidApiLevel minApi = AndroidApiLevel.B;
+  static final String EXPECTED = "";
 
-  private final String name;
-  private final DebugTestConfig config;
+  private final TestParameters parameters;
 
   @Parameters(name = "{0}")
-  public static List<Object[]> setup() {
-    DebugTestParameters parameters =
-        parameters()
-            .add("CF", temp -> testForJvm(temp).addTestClasspath().debugConfig())
-            .add(
-                "D8",
-                temp -> testForD8(temp).setMinApi(minApi).addProgramClasses(CLASS).debugConfig());
-    for (Backend backend : ToolHelper.getBackends()) {
-      parameters.add(
-          "R8/" + backend,
-          temp ->
-              testForR8(temp, backend)
-                  .noTreeShaking()
-                  .addDontObfuscate()
-                  .addKeepRules("-keepattributes SourceFile,LineNumberTable")
-                  .addProgramClasses(CLASS)
-                  .setMinApi(minApi)
-                  .debug()
-                  .debugConfig());
-    }
-    return parameters.build();
+  public static TestParametersCollection setup() {
+    return TestParameters.builder().withAllRuntimes().build();
   }
 
-  public LoadInvokeLoadOptimizationTestRunner(String name, DelayedDebugTestConfig config) {
-    this.name = name;
-    this.config = config.getConfig(temp);
+  public LoadInvokeLoadOptimizationTestRunner(TestParameters parameters) {
+    this.parameters = parameters;
   }
 
   @Test
-  public void test() throws Throwable {
+  public void testReference() throws Throwable {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm(temp)
+        .addProgramClasses(CLASS)
+        .run(parameters.getRuntime(), CLASS)
+        .assertSuccessWithOutput(EXPECTED)
+        .debugger(this::runDebugger);
+  }
+
+  @Test
+  public void testD8() throws Throwable {
+    testForD8(parameters.getBackend())
+        .setMinApi(minApi)
+        .addProgramClasses(CLASS)
+        .run(parameters.getRuntime(), CLASS)
+        .assertSuccessWithOutput(EXPECTED)
+        .debugger(this::runDebugger);
+  }
+
+  @Test
+  public void testR8() throws Throwable {
+    testForR8(parameters.getBackend())
+        .noTreeShaking()
+        .addDontObfuscate()
+        .addKeepRules("-keepattributes SourceFile,LineNumberTable")
+        .addProgramClasses(CLASS)
+        .setMinApi(minApi)
+        .debug()
+        .run(parameters.getRuntime(), CLASS)
+        .assertSuccessWithOutput(EXPECTED)
+        .debugger(this::runDebugger);
+  }
+
+  public void runDebugger(DebugTestConfig config) throws Throwable {
     Value int42 = Value.createInt(42);
     Value int7 = Value.createInt(7);
     // The test ensures that when breaking inside a function and changing a local in the parent
diff --git a/src/test/java/com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTestRunner.java b/src/test/java/com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTestRunner.java
index aa4542a..460bdd7 100644
--- a/src/test/java/com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTestRunner.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.D8;
 import com.android.tools.r8.D8Command;
 import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableList;
@@ -60,9 +61,10 @@
         checkLocal("strings"),
         checkNoLocal("objects"),
         stepOver(),
-        // Step will skip line 14 and hit 15 on JVM but will (correctly?) hit 14 on Art.
+        // Step will skip line 14 and hit 15 on JVM but will (correctly?) hit 14 on Art pre-13.
         subcommands(
-            config instanceof CfDebugTestConfig
+            (config.getRuntime().isCf()
+                    || config.getRuntime().asDex().getVersion().isNewerThanOrEqual(Version.V13_0_0))
                 ? ImmutableList.of()
                 : ImmutableList.of(checkLine(FILE, 14), stepOver())),
         checkLine(FILE, 15),
diff --git a/src/test/java/com/android/tools/r8/debug/classinit/ClassInitializationTest.java b/src/test/java/com/android/tools/r8/debug/classinit/ClassInitializationTest.java
new file mode 100644
index 0000000..1abe558
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/classinit/ClassInitializationTest.java
@@ -0,0 +1,172 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.debug.classinit;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.debug.DebugTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import org.apache.harmony.jpda.tests.framework.jdwp.Value;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// Appears to have been a regression for b/65148874, but reproduction was not possible recently.
+@RunWith(Parameterized.class)
+public class ClassInitializationTest extends DebugTestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withAllRuntimes()
+        .withApiLevel(AndroidApiLevel.B)
+        .enableApiLevelsForCf()
+        .build();
+  }
+
+  private final TestParameters parameters;
+
+  public ClassInitializationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private String fileName(Class<?> clazz) {
+    return clazz.getSimpleName() + ".java";
+  }
+
+  @Test
+  public void testStaticAssignmentInitialization() throws Throwable {
+    Class<?> clazz = ClassInitializerAssignmentInitialization.class;
+    final String SOURCE_FILE = fileName(clazz);
+    final String CLASS = typeName(clazz);
+    testForD8(parameters.getBackend())
+        .addProgramClasses(clazz)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), clazz)
+        .debugger(
+            config ->
+                runDebugTest(
+                    config,
+                    clazz,
+                    breakpoint(CLASS, "<clinit>"),
+                    run(),
+                    checkLine(SOURCE_FILE, 8),
+                    checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(0)),
+                    checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+                    checkStaticFieldClinitSafe(CLASS, "z", null, Value.createInt(0)),
+                    stepOver(),
+                    checkLine(SOURCE_FILE, 11),
+                    checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(1)),
+                    checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+                    checkStaticFieldClinitSafe(CLASS, "z", null, Value.createInt(0)),
+                    breakpoint(CLASS, "main"),
+                    run(),
+                    checkStaticField(CLASS, "x", null, Value.createInt(1)),
+                    checkStaticField(CLASS, "y", null, Value.createInt(0)),
+                    checkStaticField(CLASS, "z", null, Value.createInt(2)),
+                    run()));
+  }
+
+  @Test
+  public void testBreakpointInEmptyClassInitializer() throws Throwable {
+    Class<?> clazz = ClassInitializerEmpty.class;
+    final String SOURCE_FILE = fileName(clazz);
+    final String CLASS = typeName(clazz);
+    testForD8(parameters.getBackend())
+        .addProgramClasses(clazz)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), clazz)
+        .debugger(
+            config ->
+                runDebugTest(
+                    config,
+                    clazz,
+                    breakpoint(CLASS, "<clinit>"),
+                    run(),
+                    checkLine(SOURCE_FILE, 9),
+                    run()));
+  }
+
+  @Test
+  public void testStaticBlockInitialization() throws Throwable {
+    Class<?> clazz = ClassInitializerStaticBlockInitialization.class;
+    final String SOURCE_FILE = fileName(clazz);
+    final String CLASS = typeName(clazz);
+
+    testForD8(parameters.getBackend())
+        .addProgramClasses(clazz)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), clazz)
+        .debugger(
+            config ->
+                runDebugTest(
+                    config,
+                    CLASS,
+                    breakpoint(CLASS, "<clinit>"),
+                    run(),
+                    checkLine(SOURCE_FILE, 13),
+                    checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(0)),
+                    stepOver(),
+                    checkLine(SOURCE_FILE, 14),
+                    checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(1)),
+                    stepOver(),
+                    checkLine(SOURCE_FILE, 15),
+                    checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
+                    stepOver(),
+                    checkLine(SOURCE_FILE, 18),
+                    stepOver(),
+                    checkLine(SOURCE_FILE, 20),
+                    breakpoint(CLASS, "main"),
+                    run(),
+                    checkLine(SOURCE_FILE, 24),
+                    checkStaticField(CLASS, "x", null, Value.createInt(3)),
+                    run()));
+  }
+
+  @Test
+  public void testStaticMixedInitialization() throws Throwable {
+    Class<?> clazz = ClassInitializerMixedInitialization.class;
+    final String SOURCE_FILE = fileName(clazz);
+    final String CLASS = typeName(clazz);
+
+    testForD8(parameters.getBackend())
+        .addProgramClasses(clazz)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), clazz)
+        .debugger(
+            config ->
+                runDebugTest(
+                    config,
+                    CLASS,
+                    breakpoint(CLASS, "<clinit>"),
+                    run(),
+                    checkLine(SOURCE_FILE, 9),
+                    checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(0)),
+                    checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+                    stepOver(),
+                    checkLine(SOURCE_FILE, 13),
+                    checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(1)),
+                    checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+                    stepOver(),
+                    checkLine(SOURCE_FILE, 14),
+                    checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
+                    checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+                    stepOver(),
+                    checkLine(SOURCE_FILE, 17),
+                    checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
+                    checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+                    stepOver(),
+                    checkLine(SOURCE_FILE, 19),
+                    checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
+                    checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(2)),
+                    breakpoint(CLASS, "main"),
+                    run(),
+                    checkLine(SOURCE_FILE, 23),
+                    checkStaticField(CLASS, "x", null, Value.createInt(3)),
+                    checkStaticField(CLASS, "y", null, Value.createInt(2)),
+                    run()));
+  }
+}
diff --git a/src/test/debugTestResources/ClassInitializerAssignmentInitialization.java b/src/test/java/com/android/tools/r8/debug/classinit/ClassInitializerAssignmentInitialization.java
similarity index 77%
rename from src/test/debugTestResources/ClassInitializerAssignmentInitialization.java
rename to src/test/java/com/android/tools/r8/debug/classinit/ClassInitializerAssignmentInitialization.java
index fa6078a..2044905 100644
--- a/src/test/debugTestResources/ClassInitializerAssignmentInitialization.java
+++ b/src/test/java/com/android/tools/r8/debug/classinit/ClassInitializerAssignmentInitialization.java
@@ -1,6 +1,7 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug.classinit;
 
 public class ClassInitializerAssignmentInitialization {
 
diff --git a/src/test/java/com/android/tools/r8/debug/classinit/ClassInitializerEmpty.java b/src/test/java/com/android/tools/r8/debug/classinit/ClassInitializerEmpty.java
new file mode 100644
index 0000000..62cacf3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/classinit/ClassInitializerEmpty.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug.classinit;
+
+public class ClassInitializerEmpty {
+
+  static {
+  }
+
+  public static void main(String[] args) {}
+}
diff --git a/src/test/debugTestResources/ClassInitializerMixedInitialization.java b/src/test/java/com/android/tools/r8/debug/classinit/ClassInitializerMixedInitialization.java
similarity index 80%
rename from src/test/debugTestResources/ClassInitializerMixedInitialization.java
rename to src/test/java/com/android/tools/r8/debug/classinit/ClassInitializerMixedInitialization.java
index 504db6c..9d11f9d 100644
--- a/src/test/debugTestResources/ClassInitializerMixedInitialization.java
+++ b/src/test/java/com/android/tools/r8/debug/classinit/ClassInitializerMixedInitialization.java
@@ -1,6 +1,7 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug.classinit;
 
 public class ClassInitializerMixedInitialization {
 
diff --git a/src/test/debugTestResources/ClassInitializerStaticBlockInitialization.java b/src/test/java/com/android/tools/r8/debug/classinit/ClassInitializerStaticBlockInitialization.java
similarity index 81%
rename from src/test/debugTestResources/ClassInitializerStaticBlockInitialization.java
rename to src/test/java/com/android/tools/r8/debug/classinit/ClassInitializerStaticBlockInitialization.java
index 78fe9a0..d79ff7e 100644
--- a/src/test/debugTestResources/ClassInitializerStaticBlockInitialization.java
+++ b/src/test/java/com/android/tools/r8/debug/classinit/ClassInitializerStaticBlockInitialization.java
@@ -1,6 +1,7 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug.classinit;
 
 public class ClassInitializerStaticBlockInitialization {
 
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
index 499527f..4dff885 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
@@ -11,8 +11,6 @@
 import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.Disassemble;
 import com.android.tools.r8.Disassemble.DisassembleCommand;
-import com.android.tools.r8.JvmTestBuilder;
-import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
@@ -92,28 +90,28 @@
   @Test
   public void testJvm() throws Throwable {
     assumeTrue(parameters.isCfRuntime());
-    JvmTestBuilder builder = testForJvm().addTestClasspath();
-    builder.run(parameters.getRuntime(), CLASS).assertSuccessWithOutput(EXPECTED);
-    runDebugger(builder.debugConfig(), false);
+    testForJvm()
+        .addProgramClassesAndInnerClasses(CLASS)
+        .run(parameters.getRuntime(), CLASS)
+        .assertSuccessWithOutput(EXPECTED)
+        .debugger(config -> runDebugger(config, false));
   }
 
   @Test
   public void testR8() throws Throwable {
-    R8TestCompileResult compileResult =
-        testForR8(parameters.getBackend())
-            .addProgramClassesAndInnerClasses(CLASS)
-            .setMinApi(parameters.getApiLevel())
-            .addDontObfuscate()
-            .noTreeShaking()
-            .addKeepAllAttributes()
-            .debug()
-            .compile()
-            .assertNoMessages();
-    compileResult
+    testForR8(parameters.getBackend())
+        .addProgramClassesAndInnerClasses(CLASS)
+        .setMinApi(parameters.getApiLevel())
+        .addDontObfuscate()
+        .noTreeShaking()
+        .addKeepAllAttributes()
+        .debug()
+        .compile()
+        .assertNoMessages()
         .run(parameters.getRuntime(), CLASS)
         .assertSuccessWithOutput(EXPECTED)
-        .inspect(inspector -> assertThat(inspector.clazz(CLASS), isPresent()));
-    runDebugger(compileResult.debugConfig(), true);
+        .inspect(inspector -> assertThat(inspector.clazz(CLASS), isPresent()))
+        .debugger(config -> runDebugger(config, true));
   }
 
   @Test
@@ -168,9 +166,8 @@
         .assertNoMessages()
         .writeToZip(out2)
         .run(parameters.getRuntime(), CLASS)
-        .assertSuccessWithOutput(EXPECTED);
-
-    runDebugger(compiledResult.debugConfig(), false);
+        .assertSuccessWithOutput(EXPECTED)
+        .debugger(config -> runDebugger(config, false));
 
     Path dissasemble1 = temp.newFolder().toPath().resolve("disassemble1.txt");
     Path dissasemble2 = temp.newFolder().toPath().resolve("disassemble2.txt");
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java
index 263a87e..ce29093 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java
@@ -6,10 +6,7 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.D8TestCompileResult;
-import com.android.tools.r8.JvmTestBuilder;
 import com.android.tools.r8.R8FullTestBuilder;
-import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.debug.DebugTestBase;
@@ -88,24 +85,24 @@
   @Test
   public void testJvm() throws Throwable {
     assumeTrue(parameters.isCfRuntime());
-    JvmTestBuilder builder = testForJvm().addTestClasspath();
-    builder.run(parameters.getRuntime(), CLASS).assertSuccessWithOutput(EXPECTED);
-    runDebugger(builder.debugConfig(), false);
+    testForJvm()
+        .addProgramClassesAndInnerClasses(CLASS)
+        .run(parameters.getRuntime(), CLASS)
+        .assertSuccessWithOutput(EXPECTED)
+        .debugger(config -> runDebugger(config, false));
   }
 
   @Test
   public void testD8() throws Throwable {
     assumeTrue(parameters.isDexRuntime());
-    D8TestCompileResult compileResult =
-        testForD8()
-            .addProgramClassesAndInnerClasses(CLASS)
-            .setMinApiThreshold(AndroidApiLevel.K)
-            .compile()
-            .assertNoMessages();
-    compileResult
+    testForD8()
+        .addProgramClassesAndInnerClasses(CLASS)
+        .setMinApiThreshold(AndroidApiLevel.K)
+        .compile()
+        .assertNoMessages()
         .run(parameters.getRuntime(), CLASS)
-        .assertSuccessWithOutput(EXPECTED);
-    runDebugger(compileResult.debugConfig(), true);
+        .assertSuccessWithOutput(EXPECTED)
+        .debugger(config -> runDebugger(config, true));
   }
 
   @Test
@@ -125,8 +122,9 @@
     if (parameters.isDexRuntime()) {
       r8FullTestBuilder.setMinApiThreshold(AndroidApiLevel.K);
     }
-    R8TestCompileResult compileResult = r8FullTestBuilder.compile();
-    compileResult.run(parameters.getRuntime(), CLASS).assertSuccessWithOutput(EXPECTED);
-    runDebugger(compileResult.debugConfig(), true);
+    r8FullTestBuilder
+        .run(parameters.getRuntime(), CLASS)
+        .assertSuccessWithOutput(EXPECTED)
+        .debugger(config -> runDebugger(config, true));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvalidTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvalidTest.java
index fd59fc9..aae96ea 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvalidTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvalidTest.java
@@ -13,7 +13,6 @@
 
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
 import com.android.tools.r8.desugar.desugaredlibrary.test.DesugaredLibraryTestBuilder;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
@@ -47,7 +46,7 @@
     LibraryDesugaringSpecification jdk11InvalidLib =
         new LibraryDesugaringSpecification(
             "JDK11_INVALID_LIB",
-            ToolHelper.getUndesugaredJdk11LibJarForTesting(),
+            LibraryDesugaringSpecification.getTempLibraryJDK11Undesugar(),
             "jdk11/desugar_jdk_libs.json",
             AndroidApiLevel.L,
             LibraryDesugaringSpecification.JDK11_DESCRIPTOR,
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java
index 71026ba..9f50994 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java
@@ -5,10 +5,8 @@
 package com.android.tools.r8.desugar.desugaredlibrary;
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
-import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.containsString;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -18,7 +16,6 @@
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
-import com.android.tools.r8.errors.DesugaredLibraryMismatchDiagnostic;
 import com.android.tools.r8.origin.Origin;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
@@ -40,8 +37,7 @@
     return buildParameters(
         getTestParameters()
             .withDexRuntime(Version.first())
-            .withDexRuntime(Version.V7_0_0)
-            .withDexRuntime(Version.V8_1_0)
+            .withDefaultDexRuntime()
             .withDexRuntime(Version.last())
             .withOnlyDexRuntimeApiLevel()
             .build(),
@@ -82,8 +78,6 @@
                           containsString(
                               "The compilation is slowed down due to a mix of class file and dex"
                                   + " file inputs in the context of desugared library.")));
-                  diagnostics.assertErrorsMatch(
-                      diagnosticType(DesugaredLibraryMismatchDiagnostic.class));
                 } else {
                   diagnostics.assertNoMessages();
                 }
@@ -149,23 +143,11 @@
             .writeToZip();
 
     // Combine CF input with library desugaring with dexing without library desugaring.
-    try {
-      testForD8()
-          .addProgramFiles(desugaredLibrary)
-          .addProgramClasses(TestRunner.class)
-          .setMinApi(parameters.getApiLevel())
-          .compileWithExpectedDiagnostics(
-              diagnostics -> {
-                if (libraryDesugaringSpecification.hasAnyDesugaring(parameters)) {
-                  diagnostics.assertOnlyErrors();
-                  diagnostics.assertErrorsMatch(
-                      diagnosticType(DesugaredLibraryMismatchDiagnostic.class));
-                } else {
-                  diagnostics.assertNoMessages();
-                }
-              });
-    } catch (CompilationFailedException e) {
-    }
+    testForD8()
+        .addProgramFiles(desugaredLibrary)
+        .addProgramClasses(TestRunner.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile();
   }
 
   @Test
@@ -186,23 +168,11 @@
             .compile()
             .writeToZip();
 
-    try {
-      testForD8()
-          .addProgramFiles(libraryDex)
-          .addProgramFiles(programDex)
-          .setMinApi(parameters.getApiLevel())
-          .compileWithExpectedDiagnostics(
-              diagnostics -> {
-                if (libraryDesugaringSpecification.hasAnyDesugaring(parameters)) {
-                  diagnostics.assertOnlyErrors();
-                  diagnostics.assertErrorsMatch(
-                      diagnosticType(DesugaredLibraryMismatchDiagnostic.class));
-                } else {
-                  diagnostics.assertNoMessages();
-                }
-              });
-    } catch (CompilationFailedException e) {
-    }
+    testForD8()
+        .addProgramFiles(libraryDex)
+        .addProgramFiles(programDex)
+        .setMinApi(parameters.getApiLevel())
+        .compile();
   }
 
   @Test
@@ -247,21 +217,11 @@
             .compile()
             .writeToZip();
 
-    try {
-      testForD8()
-          .addProgramFiles(libraryDex)
-          .addProgramFiles(programDex)
-          .setMinApi(parameters.getApiLevel())
-          .compileWithExpectedDiagnostics(
-              diagnostics -> {
-                diagnostics.assertOnlyErrors();
-                diagnostics.assertErrorsMatch(
-                    allOf(
-                        diagnosticType(DesugaredLibraryMismatchDiagnostic.class),
-                        diagnosticMessage(containsString("my_group:my_artifact:1.0.9"))));
-              });
-    } catch (CompilationFailedException e) {
-    }
+    testForD8()
+        .addProgramFiles(libraryDex)
+        .addProgramFiles(programDex)
+        .setMinApi(parameters.getApiLevel())
+        .compile();
   }
 
   static class Library {}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
index c572169..bb51f2f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
@@ -100,9 +100,12 @@
           "java.util.Locale$FilteringMode",
           "java.util.SplittableRandom");
 
-  // TODO(b/238179854): Investigate how to fix these.
-  private static final Set<String> MISSING_GENERIC_TYPE_CONVERSION =
+  private static final Set<String> MISSING_GENERIC_TYPE_CONVERSION = ImmutableSet.of();
+
+  // Missing conversions in JDK8 and JDK11_LEGACY desugared library that are fixed in JDK11.
+  private static final Set<String> MISSING_GENERIC_TYPE_CONVERSION_8 =
       ImmutableSet.of(
+          "java.util.Set java.util.stream.Collector.characteristics()",
           "java.util.stream.Stream java.util.stream.Stream.flatMap(java.util.function.Function)",
           "java.util.stream.DoubleStream"
               + " java.util.stream.DoubleStream.flatMap(java.util.function.DoubleFunction)",
@@ -115,12 +118,7 @@
           "java.util.stream.LongStream"
               + " java.util.stream.Stream.flatMapToLong(java.util.function.Function)",
           "java.util.stream.LongStream"
-              + " java.util.stream.LongStream.flatMap(java.util.function.LongFunction)");
-
-  // Missing conversions in JDK8 desugared library that are fixed in JDK11 desugared library.
-  private static final Set<String> MISSING_GENERIC_TYPE_CONVERSION_8 =
-      ImmutableSet.of(
-          "java.util.Set java.util.stream.Collector.characteristics()",
+              + " java.util.stream.LongStream.flatMap(java.util.function.LongFunction)",
           "java.lang.Object java.lang.StackWalker.walk(java.util.function.Function)");
 
   // TODO(b/238179854): Investigate how to fix these.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
index 341ab5a..17ed737 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
@@ -157,7 +157,7 @@
     Path jdkLibJar =
         libraryDesugaringSpecification == JDK8
             ? ToolHelper.DESUGARED_JDK_8_LIB_JAR
-            : ToolHelper.getUndesugaredJdk11LibJarForTesting();
+            : LibraryDesugaringSpecification.getTempLibraryJDK11Undesugar();
     GenerateLintFiles.main(
         new String[] {
           libraryDesugaringSpecification.getSpecification().toString(),
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
index be8001a..c9b6885 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
@@ -14,18 +14,15 @@
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.not;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.D8TestCompileResult;
-import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.ExtractMarker;
 import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
@@ -63,35 +60,37 @@
 
   @Test
   public void testMergeDesugaredAndNonDesugared() throws Exception {
-    D8TestCompileResult compileResult;
-    try {
-      compileResult =
-          testForD8()
-              .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
-              .addProgramFiles(buildPart1DesugaredLibrary(), buildPart2NoDesugaredLibrary())
-              .setMinApi(parameters.getApiLevel())
-              .applyIf(
-                  someLibraryDesugaringRequired(),
-                  b ->
-                      b.enableCoreLibraryDesugaring(
-                          LibraryDesugaringTestConfiguration.forSpecification(
-                              libraryDesugaringSpecification.getSpecification())))
-              .compileWithExpectedDiagnostics(this::assertError)
-              .addRunClasspathFiles(
-                  getNonShrunkDesugaredLib(parameters, libraryDesugaringSpecification));
-      assertFalse(expectError());
-    } catch (CompilationFailedException e) {
-      assertTrue(expectError());
-      return;
-    }
-    assert !expectError();
-    assert compileResult != null;
+    D8TestCompileResult compileResult =
+        testForD8()
+            .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
+            .addProgramFiles(buildPart1DesugaredLibrary(), buildPart2NoDesugaredLibrary())
+            .setMinApi(parameters.getApiLevel())
+            .applyIf(
+                someLibraryDesugaringRequired(),
+                b ->
+                    b.enableCoreLibraryDesugaring(
+                        LibraryDesugaringTestConfiguration.forSpecification(
+                            libraryDesugaringSpecification.getSpecification())))
+            .compile()
+            .addRunClasspathFiles(
+                getNonShrunkDesugaredLib(parameters, libraryDesugaringSpecification));
     compileResult
         .run(parameters.getRuntime(), Part1.class)
-        .assertSuccessWithOutputLines(JAVA_RESULT);
+        .assertSuccessWithOutputLines(getExpected());
+    boolean expectNoSuchMethodError =
+        parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isOlderThan(Version.V7_0_0);
     compileResult
         .run(parameters.getRuntime(), Part2.class)
-        .assertSuccessWithOutputLines(JAVA_RESULT);
+        .assertFailureWithErrorThatThrowsIf(expectNoSuchMethodError, NoSuchMethodError.class)
+        .assertSuccessWithOutputLinesIf(!expectNoSuchMethodError, JAVA_RESULT);
+  }
+
+  private String getExpected() {
+    if (parameters.getApiLevel().getLevel() >= AndroidApiLevel.N.getLevel()) {
+      return JAVA_RESULT;
+    } else {
+      return J$_RESULT;
+    }
   }
 
   @Test
@@ -169,9 +168,17 @@
             .writeToZip();
 
     // D8 dex file output marker has the same marker as the D8 class file output.
-    // TODO(b/166617364): They should not be the same after backend is recorded and neither has
-    //  library desugaring info.
-    assertMarkersMatch(ExtractMarker.extractMarkerFromJarFile(desugaredLibDex), markerMatcher);
+    Matcher<Marker> dexMarkerMatcher =
+        allOf(
+            markerTool(Tool.D8),
+            markerCompilationMode(CompilationMode.DEBUG),
+            markerBackend(Backend.DEX),
+            markerIsDesugared(),
+            markerMinApi(parameters.getApiLevel()),
+            not(markerHasDesugaredLibraryIdentifier()));
+    List<Matcher<Marker>> markerMatcherAfterDex = ImmutableList.of(markerMatcher, dexMarkerMatcher);
+    assertMarkersMatch(
+        ExtractMarker.extractMarkerFromJarFile(desugaredLibDex), markerMatcherAfterDex);
 
     // Build an app using library desugaring merging with library not using library desugaring.
     Path app;
@@ -183,36 +190,20 @@
             .writeToZip();
 
     // When there is no class-file resources we are adding the marker for the last compilation.
-    assertMarkersMatch(
-        ExtractMarker.extractMarkerFromDexFile(app),
-        ImmutableList.of(
-            markerMatcher,
-            allOf(
-                markerTool(Tool.D8),
-                markerCompilationMode(CompilationMode.DEBUG),
-                markerBackend(Backend.DEX),
-                markerIsDesugared(),
-                markerMinApi(parameters.getApiLevel()),
-                someLibraryDesugaringRequired()
-                    ? markerHasDesugaredLibraryIdentifier()
-                    : not(markerHasDesugaredLibraryIdentifier()))));
-  }
-
-  private void assertError(TestDiagnosticMessages m) {
-    List<Diagnostic> errors = m.getErrors();
-    if (expectError()) {
-      assertEquals(1, errors.size());
-      assertTrue(
-          errors.stream()
-              .anyMatch(
-                  w ->
-                      w.getDiagnosticMessage()
-                          .contains(
-                              "The compilation is merging inputs with different"
-                                  + " desugared library desugaring")));
-    } else {
-      assertEquals(0, errors.size());
+    List<Matcher<Marker>> expectedMarkers = new ArrayList<>();
+    expectedMarkers.add(markerMatcher);
+    expectedMarkers.add(dexMarkerMatcher);
+    if (someLibraryDesugaringRequired()) {
+      expectedMarkers.add(
+          allOf(
+              markerTool(Tool.D8),
+              markerCompilationMode(CompilationMode.DEBUG),
+              markerBackend(Backend.DEX),
+              markerIsDesugared(),
+              markerMinApi(parameters.getApiLevel()),
+              markerHasDesugaredLibraryIdentifier()));
     }
+    assertMarkersMatch(ExtractMarker.extractMarkerFromDexFile(app), expectedMarkers);
   }
 
   private boolean expectError() {
@@ -241,21 +232,12 @@
             .addRunClasspathFiles(
                 getNonShrunkDesugaredLib(parameters, libraryDesugaringSpecification))
             .inspectDiagnosticMessages(this::assertWarningPresent);
-    if (parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) {
-      compileResult
-          .run(parameters.getRuntime(), Part1.class)
-          .assertSuccessWithOutputLines(J$_RESULT);
-      compileResult
-          .run(parameters.getRuntime(), Part2.class)
-          .assertSuccessWithOutputLines(J$_RESULT);
-    } else {
-      compileResult
-          .run(parameters.getRuntime(), Part1.class)
-          .assertSuccessWithOutputLines(JAVA_RESULT);
-      compileResult
-          .run(parameters.getRuntime(), Part2.class)
-          .assertSuccessWithOutputLines(JAVA_RESULT);
-    }
+    compileResult
+        .run(parameters.getRuntime(), Part1.class)
+        .assertSuccessWithOutputLines(getExpected());
+    compileResult
+        .run(parameters.getRuntime(), Part2.class)
+        .assertSuccessWithOutputLines(getExpected());
   }
 
   private void assertWarningPresent(TestDiagnosticMessages testDiagnosticMessages) {
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 9c300c7..2306edf 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
@@ -18,7 +18,6 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -83,7 +82,7 @@
     LibraryDesugaringSpecification jdk11MaxCompileSdk =
         new LibraryDesugaringSpecification(
             "JDK11_MAX",
-            ToolHelper.getUndesugaredJdk11LibJarForTesting(),
+            LibraryDesugaringSpecification.getTempLibraryJDK11Undesugar(),
             "jdk11/desugar_jdk_libs.json",
             AndroidApiLevel.LATEST,
             LibraryDesugaringSpecification.JDK11_DESCRIPTOR,
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
index 5a05e80..51222b2 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
@@ -64,7 +64,7 @@
         new LibraryDesugaringSpecification(
             "JDK11_CL",
             ImmutableSet.of(
-                ToolHelper.getUndesugaredJdk11LibJarForTesting(),
+                LibraryDesugaringSpecification.getTempLibraryJDK11Undesugar(),
                 ToolHelper.getDesugarLibConversions(LATEST),
                 ToolHelper.getCoreLambdaStubs()),
             JDK11.getSpecification(),
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FlatMapConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FlatMapConversionTest.java
new file mode 100644
index 0000000..62561e1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FlatMapConversionTest.java
@@ -0,0 +1,155 @@
+// Copyright (c) 2022, 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.conversiontests;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_LEGACY;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_MINIMAL;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CustomLibrarySpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.DoubleStream;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class FlatMapConversionTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
+
+  private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "[1, 2, 3]",
+          "[1, 2, 3]",
+          "[1.0, 2.0, 3.0]",
+          "[1, 2, 3]",
+          "[1, 2, 3]",
+          "[1.0, 2.0, 3.0]",
+          "[1, 2, 3]");
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getConversionParametersUpToExcluding(MIN_SUPPORTED),
+        ImmutableList.of(JDK8, JDK11_LEGACY, JDK11_MINIMAL, JDK11, JDK11_PATH),
+        DEFAULT_SPECIFICATIONS);
+  }
+
+  public FlatMapConversionTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
+    this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
+  }
+
+  @Test
+  public void testReference() throws Throwable {
+    Assume.assumeTrue(
+        "Run only once",
+        libraryDesugaringSpecification == JDK11 && compilationSpecification == D8_L8DEBUG);
+    testForD8()
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(Executor.class, CustomLibClass.class)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testConvert() throws Throwable {
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addProgramClasses(Executor.class)
+        .setCustomLibrarySpecification(
+            new CustomLibrarySpecification(CustomLibClass.class, MIN_SUPPORTED))
+        .addKeepMainRule(Executor.class)
+        .run(parameters.getRuntime(), Executor.class)
+        .applyIf(
+            libraryDesugaringSpecification == JDK8
+                || libraryDesugaringSpecification == JDK11_LEGACY,
+            r ->
+                r.assertFailureWithErrorThatMatches(containsString("java.lang.ClassCastException")),
+            r -> r.assertSuccessWithOutput(EXPECTED_RESULT));
+  }
+
+  static class Executor {
+
+    public static void main(String[] args) {
+      System.out.println(
+          Arrays.toString(CustomLibClass.getIntStreamAsStream().flatMap(Stream::of).toArray()));
+
+      System.out.println(
+          Arrays.toString(
+              CustomLibClass.getIntStreamAsStream().flatMapToInt(IntStream::of).toArray()));
+      System.out.println(
+          Arrays.toString(
+              CustomLibClass.getDoubleStreamAsStream()
+                  .flatMapToDouble(DoubleStream::of)
+                  .toArray()));
+      System.out.println(
+          Arrays.toString(
+              CustomLibClass.getLongStreamAsStream().flatMapToLong(LongStream::of).toArray()));
+
+      System.out.println(
+          Arrays.toString(CustomLibClass.getIntStream().flatMap(IntStream::of).toArray()));
+      System.out.println(
+          Arrays.toString(CustomLibClass.getDoubleStream().flatMap(DoubleStream::of).toArray()));
+      System.out.println(
+          Arrays.toString(CustomLibClass.getLongStream().flatMap(LongStream::of).toArray()));
+    }
+  }
+
+  // This class will be put at compilation time as library and on the runtime class path.
+  // This class is convenient for easy testing. Each method plays the role of methods in the
+  // platform APIs for which argument/return values need conversion.
+  static class CustomLibClass {
+
+    public static Stream<Integer> getIntStreamAsStream() {
+      return Arrays.stream(new Integer[] {1, 2, 3});
+    }
+
+    public static Stream<Double> getDoubleStreamAsStream() {
+      return Arrays.stream(new Double[] {1.0, 2.0, 3.0});
+    }
+
+    public static Stream<Long> getLongStreamAsStream() {
+      return Arrays.stream(new Long[] {1L, 2L, 3L});
+    }
+
+    public static IntStream getIntStream() {
+      return Arrays.stream(new int[] {1, 2, 3});
+    }
+
+    public static DoubleStream getDoubleStream() {
+      return Arrays.stream(new double[] {1.0, 2.0, 3.0});
+    }
+
+    public static LongStream getLongStream() {
+      return Arrays.stream(new long[] {1L, 2L, 3L});
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java
index 3309c38..da24386 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java
@@ -79,6 +79,11 @@
           if (!entry.getName().endsWith(".class")) {
             return;
           }
+          // TODO(b/244273080): Remove from the jar.
+          if (entry.getName().equals("sun/nio/fs/DefaultFileSystemProvider.class")
+              || entry.getName().equals("sun/nio/fs/DefaultFileTypeDetector.class")) {
+            return;
+          }
           final byte[] bytes = StreamUtils.streamToByteArrayClose(input);
           final byte[] rewrittenBytes =
               transformInvoke(entry.getName().substring(0, entry.getName().length() - 6), bytes);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
index 4b41588..e68c8bc 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
@@ -24,6 +24,9 @@
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
+import com.android.tools.r8.profile.art.ArtProfileConsumer;
+import com.android.tools.r8.profile.art.ArtProfileForRewriting;
+import com.android.tools.r8.profile.art.ArtProfileProvider;
 import com.android.tools.r8.tracereferences.TraceReferences;
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.FileUtils;
@@ -34,6 +37,7 @@
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import org.junit.Assume;
@@ -45,6 +49,7 @@
   private final LibraryDesugaringSpecification libraryDesugaringSpecification;
   private final CompilationSpecification compilationSpecification;
   private final TestCompilerBuilder<?, ?, ?, ? extends SingleTestRunResult<?>, ?> builder;
+  private List<ArtProfileForRewriting> l8ArtProfilesForRewriting = new ArrayList<>();
   private String l8ExtraKeepRules = "";
   private Consumer<InternalOptions> l8OptionModifier = ConsumerUtils.emptyConsumer();
   private boolean l8FinalPrefixVerification = true;
@@ -417,6 +422,11 @@
             compilationSpecification.isL8Shrink() && !backend.isCf() && !l8ExtraKeepRules.isEmpty(),
             b -> b.addKeepRules(l8ExtraKeepRules))
         .addOptionsModifier(l8OptionModifier);
+    for (ArtProfileForRewriting artProfileForRewriting : l8ArtProfilesForRewriting) {
+      l8Builder.addArtProfileForRewriting(
+          artProfileForRewriting.getArtProfileProvider(),
+          artProfileForRewriting.getResidualArtProfileConsumer());
+    }
   }
 
   public String collectKeepRulesWithTraceReferences(
@@ -482,4 +492,11 @@
     builder.disableDesugaring();
     return this;
   }
+
+  public DesugaredLibraryTestBuilder<?> addL8ArtProfileForRewriting(
+      ArtProfileProvider artProfileProvider, ArtProfileConsumer residualArtProfileConsumer) {
+    l8ArtProfilesForRewriting.add(
+        new ArtProfileForRewriting(artProfileProvider, residualArtProfileConsumer));
+    return this;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java
index 3ec019a..0222cb7 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.ToolHelper.DESUGARED_JDK_11_LIB_JAR;
 import static com.android.tools.r8.ToolHelper.DESUGARED_JDK_8_LIB_JAR;
 import static com.android.tools.r8.ToolHelper.DESUGARED_LIB_RELEASES_DIR;
-import static com.android.tools.r8.ToolHelper.getUndesugaredJdk11LibJarForTesting;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.CustomConversionVersion.LATEST;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.CustomConversionVersion.LEGACY;
 
@@ -14,6 +13,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.jdk11.DesugaredLibraryJDK11Undesugarer;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -29,6 +29,7 @@
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
+import org.junit.rules.TemporaryFolder;
 
 public class LibraryDesugaringSpecification {
 
@@ -89,6 +90,24 @@
     LATEST
   }
 
+  private static final Path tempLibraryJDK11Undesugar = createUndesugaredJdk11LibJarForTesting();
+
+  private static Path createUndesugaredJdk11LibJarForTesting() {
+    try {
+      TemporaryFolder staticTemp = ToolHelper.getTemporaryFolderForTest();
+      staticTemp.create();
+      Path jdklib_desugaring = staticTemp.newFolder("jdklib_desugaring").toPath();
+      return DesugaredLibraryJDK11Undesugarer.undesugaredJarJDK11(
+          jdklib_desugaring, DESUGARED_JDK_11_LIB_JAR);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static Path getTempLibraryJDK11Undesugar() {
+    return tempLibraryJDK11Undesugar;
+  }
+
   // Main head specifications.
   public static LibraryDesugaringSpecification JDK8 =
       new LibraryDesugaringSpecification(
@@ -101,7 +120,7 @@
   public static LibraryDesugaringSpecification JDK11 =
       new LibraryDesugaringSpecification(
           "JDK11",
-          getUndesugaredJdk11LibJarForTesting(),
+          tempLibraryJDK11Undesugar,
           "jdk11/desugar_jdk_libs.json",
           AndroidApiLevel.R,
           JDK11_DESCRIPTOR,
@@ -109,7 +128,7 @@
   public static LibraryDesugaringSpecification JDK11_MINIMAL =
       new LibraryDesugaringSpecification(
           "JDK11_MINIMAL",
-          getUndesugaredJdk11LibJarForTesting(),
+          tempLibraryJDK11Undesugar,
           "jdk11/desugar_jdk_libs_minimal.json",
           AndroidApiLevel.R,
           EMPTY_DESCRIPTOR_24,
@@ -117,7 +136,7 @@
   public static LibraryDesugaringSpecification JDK11_PATH =
       new LibraryDesugaringSpecification(
           "JDK11_PATH",
-          getUndesugaredJdk11LibJarForTesting(),
+          tempLibraryJDK11Undesugar,
           "jdk11/desugar_jdk_libs_nio.json",
           AndroidApiLevel.R,
           JDK11_PATH_DESCRIPTOR,
@@ -127,7 +146,7 @@
   public static LibraryDesugaringSpecification JDK11_PATH_ALTERNATIVE_3 =
       new LibraryDesugaringSpecification(
           "JDK11_PATH_ALTERNATIVE_3",
-          getUndesugaredJdk11LibJarForTesting(),
+          tempLibraryJDK11Undesugar,
           "jdk11/desugar_jdk_libs_nio_alternative_3.json",
           AndroidApiLevel.R,
           JDK11_PATH_DESCRIPTOR,
@@ -135,7 +154,7 @@
   public static LibraryDesugaringSpecification JDK11_CHM_ONLY =
       new LibraryDesugaringSpecification(
           "JDK11_CHM_ONLY",
-          getUndesugaredJdk11LibJarForTesting(),
+          tempLibraryJDK11Undesugar,
           "jdk11/chm_only_desugar_jdk_libs.json",
           AndroidApiLevel.R,
           EMPTY_DESCRIPTOR_24,
diff --git a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/RegressionB244970402.java b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/RegressionB244970402.java
new file mode 100644
index 0000000..58142a8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/RegressionB244970402.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2022, 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.staticinterfacemethod;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RegressionB244970402 extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultDexRuntime().withApiLevel(AndroidApiLevel.B).build();
+  }
+
+  private final TestParameters parameters;
+
+  public RegressionB244970402(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class, TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .addMainDexRules("-keep class * { *; }")
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("null");
+  }
+
+  interface I {
+
+    static I getInstance() {
+      return null;
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(I.getInstance());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
index f774b57..fae6781 100644
--- a/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
@@ -10,8 +10,10 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.examples.JavaExampleClassProxy;
 import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ZipUtils;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
@@ -94,6 +96,7 @@
   public void testD8() throws Exception {
     testForD8(parameters.getBackend())
         .addProgramFiles(getProgramInputs())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST))
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN.typeName(), getZipFile())
         .assertSuccessWithOutput(EXPECTED)
@@ -106,7 +109,7 @@
               // TODO(b/168568827): Once we support a nested addSuppressed this will increase.
               int expectedSynthetics =
                   parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport())
-                      ? 2
+                      ? 3
                       : 0;
               assertEquals(INPUT_CLASSES + expectedSynthetics, inspector.allClasses().size());
             });
@@ -117,12 +120,9 @@
     assumeTrue(parameters.isDexRuntime());
     testForR8(parameters.getBackend())
         .addProgramFiles(getProgramInputs())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST))
         .addKeepMainRule(MAIN.typeName())
         .addKeepClassAndMembersRules(FOO.typeName(), BAR.typeName())
-        // TODO(b/214250388): Don't warn about synthetic code.
-        .applyIf(
-            parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport()),
-            builder -> builder.addDontWarn("java.lang.AutoCloseable"))
         .setMinApi(parameters.getApiLevel())
         .addDontObfuscate()
         .run(parameters.getRuntime(), MAIN.typeName(), getZipFile())
@@ -141,11 +141,7 @@
               if (parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport())) {
                 Set<String> classOutputWithSynthetics = new HashSet<>(nonSyntheticClassOutput);
                 classOutputWithSynthetics.add(
-                    SyntheticItemsTestUtils.syntheticBackportClass(BAR.getClassReference(), 0)
-                        .getTypeName());
-                classOutputWithSynthetics.add(
-                    SyntheticItemsTestUtils.syntheticTwrCloseResourceClass(
-                            BAR.getClassReference(), 1)
+                    SyntheticItemsTestUtils.syntheticApiOutlineClass(BAR.getClassReference(), 0)
                         .getTypeName());
                 assertEquals(classOutputWithSynthetics, foundClasses);
               } else {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/PrivateEnumWithPrivateInvokeSpecialTest.java b/src/test/java/com/android/tools/r8/enumunboxing/PrivateEnumWithPrivateInvokeSpecialTest.java
new file mode 100644
index 0000000..3943993
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/PrivateEnumWithPrivateInvokeSpecialTest.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing;
+
+import 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.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/** This is a reproduction of b/245096779 */
+@RunWith(Parameterized.class)
+public class PrivateEnumWithPrivateInvokeSpecialTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("FOO");
+  }
+
+  private static class Main {
+
+    private enum MyEnum {
+      FOO,
+      BAR;
+
+      private boolean isFoo() {
+        return this == FOO;
+      }
+
+      public MyEnum compute() {
+        if (isFoo()) {
+          return BAR;
+        }
+        return FOO;
+      }
+    }
+
+    public static void main(String[] args) {
+      MyEnum myEnum = args.length > 0 ? MyEnum.FOO : MyEnum.BAR;
+      if (myEnum.compute().isFoo()) {
+        System.out.println("FOO");
+      } else {
+        System.out.println("BAR");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToEnumUnboxedMethodTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToEnumUnboxedMethodTest.java
index c40df02..b9ee0f5 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToEnumUnboxedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToEnumUnboxedMethodTest.java
@@ -6,10 +6,7 @@
 
 import static com.android.tools.r8.graph.invokespecial.InvokeSpecialToEnumUnboxedMethodTest.MyEnum.TEST_1;
 import static com.android.tools.r8.graph.invokespecial.InvokeSpecialToEnumUnboxedMethodTest.MyEnum.TEST_2;
-import static org.junit.Assert.assertThrows;
 
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.DiagnosticsMatcher;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -32,19 +29,13 @@
 
   @Test
   public void testR8() throws Exception {
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            testForR8Compat(parameters.getBackend())
-                .addInnerClasses(getClass())
-                .setMinApi(parameters.getApiLevel())
-                .addKeepMainRule(Main.class)
-                .enableInliningAnnotations()
-                // TODO(b/235817866): Should not have invalid assert.
-                .compileWithExpectedDiagnostics(
-                    diagnostics ->
-                        diagnostics.assertErrorThatMatches(
-                            DiagnosticsMatcher.diagnosticException(AssertionError.class))));
+    testForR8Compat(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Foo");
   }
 
   public enum MyEnum {
@@ -63,6 +54,8 @@
     }
   }
 
+  // This is similar to PrivateEnumWithPrivateInvokeSpecialTest by invoking a private method on an
+  // enum that is unboxed.
   public static class Main {
 
     public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/internal/Chrome200430Test.java b/src/test/java/com/android/tools/r8/internal/Chrome200430Test.java
index 6314424..4e7c6e3 100644
--- a/src/test/java/com/android/tools/r8/internal/Chrome200430Test.java
+++ b/src/test/java/com/android/tools/r8/internal/Chrome200430Test.java
@@ -34,6 +34,10 @@
         .addProgramFiles(getProgramFiles())
         .addLibraryFiles(getLibraryFiles())
         .addKeepRuleFiles(getKeepRuleFiles())
+        .addOptionsModification(
+            options -> options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
+        .allowUnnecessaryDontWarnWildcards()
+        .allowUnusedDontWarnPatterns()
         .allowUnusedProguardConfigurationRules()
         .setMinApi(AndroidApiLevel.N)
         .compile();
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
index 9ea0db9..262f164 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -24,9 +25,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
 
-// TODO(b/112437944): Strengthen test to ensure that builder inlining succeeds even without single-
-//  and double-caller inlining.
 @RunWith(Parameterized.class)
 public class Proto2BuilderShrinkingTest extends ProtoShrinkingTestBase {
 
@@ -36,10 +37,13 @@
   private static List<Path> PROGRAM_FILES =
       ImmutableList.of(PROTO2_EXAMPLES_JAR, PROTO2_PROTO_JAR, PROTOBUF_LITE_JAR);
 
-  private final List<String> mains;
-  private final TestParameters parameters;
+  @Parameter(0)
+  public List<String> mains;
 
-  @Parameterized.Parameters(name = "{1}, {0}")
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, {0}")
   public static List<Object[]> data() {
     return buildParameters(
         ImmutableList.of(
@@ -59,11 +63,6 @@
         getTestParameters().withDefaultDexRuntime().withAllApiLevels().build());
   }
 
-  public Proto2BuilderShrinkingTest(List<String> mains, TestParameters parameters) {
-    this.mains = mains;
-    this.parameters = parameters;
-  }
-
   @Test
   public void test() throws Exception {
     R8TestCompileResult result =
@@ -187,18 +186,46 @@
   }
 
   private void verifyMethodToInvokeValuesAreAbsent(CodeInspector outputInspector) {
+    ClassSubject generatedMessageLiteClassSubject =
+        outputInspector.clazz("com.google.protobuf.GeneratedMessageLite");
+    assertThat(generatedMessageLiteClassSubject, isPresent());
+
+    MethodSubject isInitializedMethodSubject =
+        generatedMessageLiteClassSubject.uniqueMethodWithName("isInitialized");
+
     DexType methodToInvokeType =
-        outputInspector.clazz(METHOD_TO_INVOKE_ENUM).getDexProgramClass().type;
+        outputInspector.clazz(METHOD_TO_INVOKE_ENUM).getDexProgramClass().getType();
     for (String main : mains) {
       MethodSubject mainMethodSubject = outputInspector.clazz(main).mainMethod();
       assertThat(mainMethodSubject, isPresent());
+
+      // Verify that the calls to GeneratedMessageLite.createBuilder() have been inlined.
+      assertTrue(
+          mainMethodSubject
+              .streamInstructions()
+              .filter(InstructionSubject::isInvoke)
+              .map(InstructionSubject::getMethod)
+              .allMatch(
+                  method ->
+                      method.getHolderType()
+                              != generatedMessageLiteClassSubject.getDexProgramClass().getType()
+                          || (isInitializedMethodSubject.isPresent()
+                              && method
+                                  == isInitializedMethodSubject
+                                      .getProgramMethod()
+                                      .getReference())));
+
+      // Verify that there are no accesses to MethodToInvoke after inlining createBuilder() -- and
+      // specifically no accesses to MethodToInvoke.NEW_BUILDER.
       assertTrue(
           main,
           mainMethodSubject
               .streamInstructions()
               .filter(InstructionSubject::isStaticGet)
-              .map(instruction -> instruction.getField().type)
+              .map(instruction -> instruction.getField().getType())
               .noneMatch(methodToInvokeType::equals));
+
+      // Verify that there is no switches on the ordinal of a MethodToInvoke instance.
       assertTrue(mainMethodSubject.streamInstructions().noneMatch(InstructionSubject::isSwitch));
     }
   }
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/AssertionErrorMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/AssertionErrorMethods.java
new file mode 100644
index 0000000..7b8c096
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/AssertionErrorMethods.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.backports;
+
+import java.lang.reflect.Constructor;
+
+public final class AssertionErrorMethods {
+
+  public static AssertionError createAssertionError(String message, Throwable cause) {
+    try {
+      Constructor<AssertionError> twoArgsConstructor =
+          AssertionError.class.getDeclaredConstructor(String.class, Throwable.class);
+      return twoArgsConstructor.newInstance(message, cause);
+    } catch (Exception e) {
+      return new AssertionError(message);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/CloseResourceMethod.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/CloseResourceMethod.java
index fb6a937..2059e86 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/CloseResourceMethod.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/CloseResourceMethod.java
@@ -39,11 +39,11 @@
           Method method = resource.getClass().getMethod("close");
           method.invoke(resource);
         } catch (NoSuchMethodException | SecurityException e) {
-          throw new AssertionError(resource.getClass() + " does not have a close() method.", e);
+          throw new RuntimeException(resource.getClass() + " does not have a close() method.", e);
         } catch (IllegalAccessException
             | IllegalArgumentException
             | ExceptionInInitializerError e) {
-          throw new AssertionError("Fail to call close() on " + resource.getClass(), e);
+          throw new RuntimeException("Fail to call close() on " + resource.getClass(), e);
         } catch (InvocationTargetException e) {
           throw e.getCause();
         }
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index 81f92c3..429f2be 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -34,6 +34,7 @@
       factory.createType("Lcom/android/tools/r8/ir/desugar/backports/BackportedMethods;");
   private final List<Class<?>> METHOD_TEMPLATE_CLASSES =
       ImmutableList.of(
+          AssertionErrorMethods.class,
           AtomicReferenceArrayMethods.class,
           AtomicReferenceFieldUpdaterMethods.class,
           AtomicReferenceMethods.class,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ConstructorWithNonTrivialControlFlowTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ConstructorWithNonTrivialControlFlowTest.java
index 26bbf63..9292441 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ConstructorWithNonTrivialControlFlowTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ConstructorWithNonTrivialControlFlowTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.apimodel.ApiModelingTestHelper;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -45,6 +46,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(ConstructorWithNonTrivialControlFlowTest.class)
         .addKeepMainRule(TestClass.class)
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .addOptionsModification(options -> options.enableClassInlining = enableClassInlining)
         .enableInliningAnnotations()
         .enableMemberValuePropagationAnnotations()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java
index b21bcab..e4b2d91 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java
@@ -54,6 +54,9 @@
   }
 
   private void inspect(CodeInspector inspector) {
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+
     ClassSubject bClassSubject = inspector.clazz(B.class);
     assertThat(bClassSubject, isPresent());
 
@@ -63,7 +66,8 @@
     assertTrue(
         negativeTestSubject.streamInstructions().noneMatch(InstructionSubject::isInvokeVirtual));
 
-    MethodSubject positiveTestSubject = bClassSubject.uniqueMethodWithName("positiveTest");
+    // B.positiveTest() is moved to A as a result of bridge hoisting.
+    MethodSubject positiveTestSubject = aClassSubject.uniqueMethodWithName("positiveTest");
     assertThat(positiveTestSubject, isPresent());
     assertTrue(positiveTestSubject.streamInstructions().noneMatch(this::isInvokeSuper));
     assertTrue(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java
index 6037e95..a1cf0e5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java
@@ -11,13 +11,18 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
+import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.apimodel.ApiModelingTestHelper;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.UnverifiableCfCodeDiagnostic;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.Streams;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -61,36 +66,62 @@
   }
 
   @Test
+  public void testNoInlineNonExistingCatchPreLWithApiModeling() throws Exception {
+    setupR8TestBuilder(
+        b ->
+            b.apply(ApiModelingTestHelper::enableOutliningOfMethods)
+                .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+                .apply(ApiModelingTestHelper::enableApiCallerIdentification),
+        inspector -> {
+          ClassSubject classSubject = inspector.clazz(TestClassCallingMethodWithNonExisting.class);
+          boolean hasCatchHandler =
+              Streams.stream(classSubject.mainMethod().iterateTryCatches()).count() > 0;
+          // The catch handler does not exist in ClassWithCatchNonExisting.methodWithCatch thus we
+          // assign UNKNOWN api level. As a result we do not inline.
+          assertFalse(hasCatchHandler);
+        });
+  }
+
+  @Test
   public void testNoInlineNonExistingCatchPreL() throws Exception {
-    R8TestRunResult result =
-        testForR8(parameters.getBackend())
-            .addProgramClasses(
-                TestClassCallingMethodWithNonExisting.class,
-                ClassWithCatchNonExisting.class,
-                ExistingException.class)
-            .addKeepMainRule(TestClassCallingMethodWithNonExisting.class)
-            .addDontWarn(NonExistingException.class)
-            .allowDiagnosticWarningMessages()
-            .setMinApi(parameters.getApiLevel())
-            .compileWithExpectedDiagnostics(
-                diagnostics ->
-                    diagnostics.assertWarningsMatch(
-                        allOf(
-                            diagnosticType(UnverifiableCfCodeDiagnostic.class),
-                            diagnosticMessage(
-                                containsString(
-                                    "Unverifiable code in `void "
-                                        + ClassWithCatchNonExisting.class.getTypeName()
-                                        + ".methodWithCatch()`")))))
-            .run(parameters.getRuntime(), TestClassCallingMethodWithNonExisting.class)
-            .assertSuccess();
-    ClassSubject classSubject =
-        result.inspector().clazz(TestClassCallingMethodWithNonExisting.class);
-    boolean hasCatchHandler =
-        Streams.stream(classSubject.mainMethod().iterateTryCatches()).count() > 0;
-    // The catch handler does not exist in ClassWithCatchNonExisting.methodWithCatch thus we assign
-    // UNKNOWN api level. As a result we do not inline.
-    assertFalse(hasCatchHandler);
+    setupR8TestBuilder(
+        b -> b.apply(ApiModelingTestHelper::disableApiModeling),
+        inspector -> {
+          ClassSubject classSubject = inspector.clazz(TestClassCallingMethodWithNonExisting.class);
+          boolean hasCatchHandler =
+              Streams.stream(classSubject.mainMethod().iterateTryCatches()).count() > 0;
+          int runtimeLevel = parameters.getApiLevel().getLevel();
+          assertEquals(runtimeLevel >= AndroidApiLevel.L.getLevel(), hasCatchHandler);
+        });
+  }
+
+  private void setupR8TestBuilder(
+      ThrowableConsumer<R8FullTestBuilder> r8TestBuilderConsumer,
+      ThrowingConsumer<CodeInspector, ? extends Exception> inspect)
+      throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(
+            TestClassCallingMethodWithNonExisting.class,
+            ClassWithCatchNonExisting.class,
+            ExistingException.class)
+        .addKeepMainRule(TestClassCallingMethodWithNonExisting.class)
+        .addDontWarn(NonExistingException.class)
+        .allowDiagnosticWarningMessages()
+        .setMinApi(parameters.getApiLevel())
+        .apply(r8TestBuilderConsumer)
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics.assertWarningsMatch(
+                    allOf(
+                        diagnosticType(UnverifiableCfCodeDiagnostic.class),
+                        diagnosticMessage(
+                            containsString(
+                                "Unverifiable code in `void "
+                                    + ClassWithCatchNonExisting.class.getTypeName()
+                                    + ".methodWithCatch()`")))))
+        .run(parameters.getRuntime(), TestClassCallingMethodWithNonExisting.class)
+        .assertSuccess()
+        .inspect(inspect);
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
index 1f51c2f..e0a1d16 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
@@ -46,10 +46,9 @@
         .addInnerClasses(SingleTargetAfterInliningTest.class)
         .addKeepMainRule(TestClass.class)
         .addOptionsModification(
-            options -> {
-              options.inlinerOptions().applyInliningToInlinee = true;
-              options.inlinerOptions().applyInliningToInlineeMaxDepth = maxInliningDepth;
-            })
+            options ->
+                options.inlinerOptions().applyInliningToInlineePredicateForTesting =
+                    (appView, inlinee, inliningDepth) -> true)
         .enableAlwaysInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineChainTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineChainTest.java
index 011cc58..9fdb8ca 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineChainTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineChainTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.apimodel.ApiModelingTestHelper;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -62,6 +63,7 @@
         .allowDiagnosticWarningMessages()
         .setMinApi(parameters.getApiLevel())
         .addDontObfuscate()
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .compile()
         .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
         .run(parameters.getRuntime(), MAIN, "foobar")
diff --git a/src/test/java/com/android/tools/r8/lightir/LIRBasicCallbackTest.java b/src/test/java/com/android/tools/r8/lightir/LIRBasicCallbackTest.java
index 1273bfd..c8e9e8a 100644
--- a/src/test/java/com/android/tools/r8/lightir/LIRBasicCallbackTest.java
+++ b/src/test/java/com/android/tools/r8/lightir/LIRBasicCallbackTest.java
@@ -41,7 +41,11 @@
                 method,
                 v -> {
                   throw new Unreachable();
-                })
+                },
+                b -> {
+                  throw new Unreachable();
+                },
+                factory)
             .setMetadata(IRMetadata.unknown())
             .addConstNull()
             .addConstInt(42)
diff --git a/src/test/java/com/android/tools/r8/lightir/LIRRoundtripTest.java b/src/test/java/com/android/tools/r8/lightir/LIRRoundtripTest.java
index a169bfc..1a8d281 100644
--- a/src/test/java/com/android/tools/r8/lightir/LIRRoundtripTest.java
+++ b/src/test/java/com/android/tools/r8/lightir/LIRRoundtripTest.java
@@ -5,6 +5,7 @@
 
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -18,7 +19,13 @@
 
   static class TestClass {
     public static void main(String[] args) {
-      System.out.println("Hello, world!");
+      String message = "Hello, world!";
+      try {
+        System.out.println(42 / (args.length == 0 ? 0 : 1));
+        message = "Oh no!";
+      } catch (ArithmeticException ignored) {
+      }
+      System.out.println(message);
     }
   }
 
@@ -57,7 +64,8 @@
         .assertSuccessWithOutputLines("Hello, world!");
   }
 
-  @Test
+  // TODO(b/225838009): Support debug local info.
+  @Test(expected = CompilationFailedException.class)
   public void testRoundtripDebug() throws Exception {
     testForD8(parameters.getBackend())
         .debug()
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/StaticFieldClassInitMemberRebindingTest.java b/src/test/java/com/android/tools/r8/memberrebinding/StaticFieldClassInitMemberRebindingTest.java
index c31c93d..7ab14c7 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/StaticFieldClassInitMemberRebindingTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/StaticFieldClassInitMemberRebindingTest.java
@@ -47,7 +47,6 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
-        .addOptionsModification(options -> options.enableVisibilityBridgeRemoval = false)
         .run(parameters.getRuntime(), Main.class)
         // TODO(b/220668540): R8 should not change class loading semantics.
         .assertSuccessWithOutputLines(R8_EXPECTED);
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
index 87a2fb2..9501ae9 100644
--- a/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
+++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
@@ -13,6 +13,8 @@
 import com.android.tools.r8.debug.DebugTestBase;
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.debug.DexDebugTestConfig;
+import com.android.tools.r8.debug.classinit.ClassInitializationTest;
+import com.android.tools.r8.debug.classinit.ClassInitializerEmpty;
 import com.android.tools.r8.shaking.ProguardKeepRule;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableList;
@@ -48,6 +50,7 @@
                         ImmutableList.of("SourceFile", "LineNumberTable"));
                   })
               .addProgramFiles(DEBUGGEE_JAR)
+              .addProgramFiles(ToolHelper.getClassFileForTestClass(ClassInitializerEmpty.class))
               .setMode(CompilationMode.DEBUG)
               .setProguardMapOutputPath(proguardMapPath);
       DebugTestConfig config;
@@ -82,19 +85,16 @@
     this.backend = backend;
   }
 
-  /**
-   * replica of {@link
-   * com.android.tools.r8.debug.ClassInitializationTest#testBreakpointInEmptyClassInitializer}
-   */
+  /** replica of {@link ClassInitializationTest#testBreakpointInEmptyClassInitializer} */
   @Test
   public void testBreakpointInEmptyClassInitializer() throws Throwable {
-    final String CLASS = "ClassInitializerEmpty";
+    final String CLASS = typeName(ClassInitializerEmpty.class);
     runDebugTest(
         configs.get(backend),
         CLASS,
         breakpoint(CLASS, "<clinit>"),
         run(),
-        checkLine(TEST_FILE, 8),
+        checkLine(TEST_FILE, 9),
         run());
   }
 
diff --git a/src/test/java/com/android/tools/r8/profile/art/ArtProfileCollisionAfterClassMergingRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/ArtProfileCollisionAfterClassMergingRewritingTest.java
new file mode 100644
index 0000000..15ed09c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/ArtProfileCollisionAfterClassMergingRewritingTest.java
@@ -0,0 +1,147 @@
+// Copyright (c) 2022, 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.profile.art;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.Lists;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ArtProfileCollisionAfterClassMergingRewritingTest extends TestBase {
+
+  private static final ClassReference mainClassReference = Reference.classFromClass(Main.class);
+  private static final MethodReference mainMethodReference =
+      MethodReferenceUtils.mainMethod(Main.class);
+
+  private static final ClassReference fooClassReference = Reference.classFromClass(Foo.class);
+  private static final MethodReference helloMethodReference =
+      MethodReferenceUtils.methodFromMethod(Foo.class, "hello");
+
+  private static final ClassReference barClassReference = Reference.classFromClass(Bar.class);
+  private static final MethodReference worldMethodReference =
+      MethodReferenceUtils.methodFromMethod(Bar.class, "world");
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    MyArtProfileProvider artProfileProvider = new MyArtProfileProvider();
+    ArtProfileConsumerForTesting residualArtProfileConsumer = new ArtProfileConsumerForTesting();
+    testForR8(Backend.DEX)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addArtProfileForRewriting(artProfileProvider, residualArtProfileConsumer)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertMergedInto(Foo.class, Bar.class).assertNoOtherClassesMerged())
+        .enableInliningAnnotations()
+        .setMinApi(AndroidApiLevel.LATEST)
+        .compile()
+        .inspect(inspector -> inspect(inspector, residualArtProfileConsumer));
+  }
+
+  private void inspect(
+      CodeInspector inspector, ArtProfileConsumerForTesting residualArtProfileConsumer) {
+    ClassSubject barClassSubject = inspector.clazz(Bar.class);
+    assertThat(barClassSubject, isPresentAndRenamed());
+
+    MethodSubject helloMethodSubject = barClassSubject.uniqueMethodWithName("hello");
+    assertThat(helloMethodSubject, isPresentAndRenamed());
+
+    MethodSubject worldMethodSubject = barClassSubject.uniqueMethodWithName("world");
+    assertThat(worldMethodSubject, isPresentAndRenamed());
+
+    assertTrue(residualArtProfileConsumer.finished);
+    assertEquals(
+        Lists.newArrayList(
+            mainClassReference,
+            mainMethodReference,
+            barClassSubject.getFinalReference(),
+            helloMethodSubject.getFinalReference(),
+            worldMethodSubject.getFinalReference()),
+        residualArtProfileConsumer.references);
+    assertEquals(
+        Lists.newArrayList(
+            ArtProfileClassRuleInfoImpl.empty(),
+            ArtProfileMethodRuleInfoImpl.empty(),
+            ArtProfileClassRuleInfoImpl.empty(),
+            ArtProfileMethodRuleInfoImpl.empty(),
+            ArtProfileMethodRuleInfoImpl.empty()),
+        residualArtProfileConsumer.infos);
+  }
+
+  static class MyArtProfileProvider implements ArtProfileProvider {
+
+    @Override
+    public void getArtProfile(ArtProfileBuilder profileBuilder) {
+      profileBuilder
+          .addClassRule(classRuleBuilder -> classRuleBuilder.setClassReference(mainClassReference))
+          .addMethodRule(
+              methodRuleBuilder -> methodRuleBuilder.setMethodReference(mainMethodReference))
+          .addClassRule(classRuleBuilder -> classRuleBuilder.setClassReference(fooClassReference))
+          .addMethodRule(
+              methodRuleBuilder -> methodRuleBuilder.setMethodReference(helloMethodReference))
+          .addClassRule(classRuleBuilder -> classRuleBuilder.setClassReference(barClassReference))
+          .addMethodRule(
+              methodRuleBuilder -> methodRuleBuilder.setMethodReference(worldMethodReference));
+    }
+
+    @Override
+    public Origin getOrigin() {
+      return Origin.unknown();
+    }
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      Foo.hello();
+      Bar.world();
+    }
+  }
+
+  static class Foo {
+
+    @NeverInline
+    static void hello() {
+      System.out.print("Hello");
+    }
+  }
+
+  static class Bar {
+
+    @NeverInline
+    static void world() {
+      System.out.println(", world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/ArtProfileConsumerForTesting.java b/src/test/java/com/android/tools/r8/profile/art/ArtProfileConsumerForTesting.java
new file mode 100644
index 0000000..2a0a041
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/ArtProfileConsumerForTesting.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2022, 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.profile.art;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ArtProfileConsumerForTesting implements ArtProfileConsumer {
+
+  boolean finished;
+  List<Object> references = new ArrayList<>();
+  List<Object> infos = new ArrayList<>();
+
+  @Override
+  public ArtProfileRuleConsumer getRuleConsumer() {
+    return new ArtProfileRuleConsumer() {
+
+      @Override
+      public void acceptClassRule(
+          ClassReference classReference, ArtProfileClassRuleInfo classRuleInfo) {
+        references.add(classReference);
+        infos.add(classRuleInfo);
+      }
+
+      @Override
+      public void acceptMethodRule(
+          MethodReference methodReference, ArtProfileMethodRuleInfo methodRuleInfo) {
+        references.add(methodReference);
+        infos.add(methodRuleInfo);
+      }
+    };
+  }
+
+  @Override
+  public void finished(DiagnosticsHandler handler) {
+    finished = true;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/ArtProfilePassthroughTest.java b/src/test/java/com/android/tools/r8/profile/art/ArtProfilePassthroughTest.java
new file mode 100644
index 0000000..b45ba38
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/ArtProfilePassthroughTest.java
@@ -0,0 +1,141 @@
+// Copyright (c) 2022, 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.profile.art;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.google.common.collect.Lists;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ArtProfilePassthroughTest extends TestBase {
+
+  enum ProviderStatus {
+    PENDING,
+    ACTIVE,
+    DONE
+  }
+
+  private static final ClassReference mainClassReference = Reference.classFromClass(Main.class);
+  private static final MethodReference mainMethodReference =
+      MethodReferenceUtils.mainMethod(Main.class);
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    ArtProfileProviderForTesting artProfileProvider = new ArtProfileProviderForTesting();
+    ArtProfileConsumerForTesting residualArtProfileConsumer =
+        new ArtProfileConsumerForTesting(artProfileProvider);
+    testForD8(Backend.DEX)
+        .addProgramClasses(Main.class)
+        // Add a profile provider and consumer that verifies that the profile is being provided at
+        // the same time it is being consumed.
+        .addArtProfileForRewriting(artProfileProvider, residualArtProfileConsumer)
+        .release()
+        .setMinApi(AndroidApiLevel.LATEST)
+        .compile();
+
+    // Verify that the profile input was processed.
+    assertEquals(ProviderStatus.DONE, artProfileProvider.providerStatus);
+    assertTrue(residualArtProfileConsumer.finished);
+    assertEquals(
+        Lists.newArrayList(mainClassReference, mainMethodReference),
+        residualArtProfileConsumer.references);
+  }
+
+  static class ArtProfileProviderForTesting implements ArtProfileProvider {
+
+    ProviderStatus providerStatus = ProviderStatus.PENDING;
+
+    @Override
+    public void getArtProfile(ArtProfileBuilder profileBuilder) {
+      providerStatus = ProviderStatus.ACTIVE;
+      profileBuilder
+          .addClassRule(classRuleBuilder -> classRuleBuilder.setClassReference(mainClassReference))
+          .addMethodRule(
+              methodRuleBuilder -> methodRuleBuilder.setMethodReference(mainMethodReference));
+      providerStatus = ProviderStatus.DONE;
+    }
+
+    @Override
+    public Origin getOrigin() {
+      return Origin.unknown();
+    }
+  }
+
+  static class ArtProfileConsumerForTesting implements ArtProfileConsumer {
+
+    private final ArtProfileProviderForTesting artProfileProvider;
+
+    List<Object> references = new ArrayList<>();
+    boolean finished;
+
+    ArtProfileConsumerForTesting(ArtProfileProviderForTesting artProfileProvider) {
+      this.artProfileProvider = artProfileProvider;
+    }
+
+    @Override
+    public ArtProfileRuleConsumer getRuleConsumer() {
+      // The compiler should not request to get the profile from the provider before getting the
+      // consumer.
+      assertTrue(artProfileProvider.providerStatus == ProviderStatus.PENDING);
+      return new ArtProfileRuleConsumer() {
+        @Override
+        public void acceptClassRule(
+            ClassReference classReference, ArtProfileClassRuleInfo classRuleInfo) {
+          // The consumer should only be receiving callbacks while the profile is being
+          // provided.
+          assertTrue(artProfileProvider.providerStatus == ProviderStatus.ACTIVE);
+          references.add(classReference);
+        }
+
+        @Override
+        public void acceptMethodRule(
+            MethodReference methodReference, ArtProfileMethodRuleInfo methodRuleInfo) {
+          // The consumer should only be receiving callbacks while the profile is being
+          // provided.
+          assertTrue(artProfileProvider.providerStatus == ProviderStatus.ACTIVE);
+          references.add(methodReference);
+        }
+      };
+    }
+
+    @Override
+    public void finished(DiagnosticsHandler handler) {
+      // The profile should be fully provided when all data is given to the consumer.
+      assertTrue(artProfileProvider.providerStatus == ProviderStatus.DONE);
+      finished = true;
+    }
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/ArtProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/ArtProfileRewritingTest.java
new file mode 100644
index 0000000..80bb288
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/ArtProfileRewritingTest.java
@@ -0,0 +1,134 @@
+// Copyright (c) 2022, 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.profile.art;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.Lists;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ArtProfileRewritingTest extends TestBase {
+
+  private static final ClassReference mainClassReference = Reference.classFromClass(Main.class);
+  private static final MethodReference mainMethodReference =
+      MethodReferenceUtils.mainMethod(Main.class);
+
+  private static final ClassReference greeterClassReference =
+      Reference.classFromClass(Greeter.class);
+  private static final MethodReference greetMethodReference =
+      MethodReferenceUtils.methodFromMethod(Greeter.class, "greet");
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    ArtProfileProviderForTesting artProfileProvider = new ArtProfileProviderForTesting();
+    ArtProfileConsumerForTesting residualArtProfileConsumer = new ArtProfileConsumerForTesting();
+    testForR8(Backend.DEX)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addArtProfileForRewriting(artProfileProvider, residualArtProfileConsumer)
+        .enableInliningAnnotations()
+        .setMinApi(AndroidApiLevel.LATEST)
+        .compile()
+        .inspect(inspector -> inspect(inspector, residualArtProfileConsumer));
+  }
+
+  private void inspect(
+      CodeInspector inspector, ArtProfileConsumerForTesting residualArtProfileConsumer) {
+    ClassSubject greeterClassSubject = inspector.clazz(Greeter.class);
+    assertThat(greeterClassSubject, isPresentAndRenamed());
+
+    MethodSubject greetMethodSubject = greeterClassSubject.uniqueMethodWithName("greet");
+    assertThat(greetMethodSubject, isPresentAndRenamed());
+
+    assertTrue(residualArtProfileConsumer.finished);
+    assertEquals(
+        Lists.newArrayList(
+            mainClassReference,
+            mainMethodReference,
+            greeterClassSubject.getFinalReference(),
+            greetMethodSubject.getFinalReference()),
+        residualArtProfileConsumer.references);
+    assertEquals(
+        Lists.newArrayList(
+            ArtProfileClassRuleInfoImpl.empty(),
+            ArtProfileMethodRuleInfoImpl.builder().setIsStartup().build(),
+            ArtProfileClassRuleInfoImpl.empty(),
+            ArtProfileMethodRuleInfoImpl.builder().setIsHot().setIsPostStartup().build()),
+        residualArtProfileConsumer.infos);
+  }
+
+  static class ArtProfileProviderForTesting implements ArtProfileProvider {
+
+    @Override
+    public void getArtProfile(ArtProfileBuilder profileBuilder) {
+      profileBuilder
+          .addClassRule(classRuleBuilder -> classRuleBuilder.setClassReference(mainClassReference))
+          .addMethodRule(
+              methodRuleBuilder ->
+                  methodRuleBuilder
+                      .setMethodReference(mainMethodReference)
+                      .setMethodRuleInfo(
+                          methodRuleInfoBuilder -> methodRuleInfoBuilder.setIsStartup(true)))
+          .addClassRule(
+              classRuleBuilder -> classRuleBuilder.setClassReference(greeterClassReference))
+          .addMethodRule(
+              methodRuleBuilder ->
+                  methodRuleBuilder
+                      .setMethodReference(greetMethodReference)
+                      .setMethodRuleInfo(
+                          methodRuleInfoBuilder ->
+                              methodRuleInfoBuilder.setIsHot(true).setIsPostStartup(true)));
+    }
+
+    @Override
+    public Origin getOrigin() {
+      return Origin.unknown();
+    }
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      Greeter.greet();
+    }
+  }
+
+  static class Greeter {
+
+    @NeverInline
+    static void greet() {
+      System.out.println("Hello, world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/DesugaredLibraryArtProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/DesugaredLibraryArtProfileRewritingTest.java
new file mode 100644
index 0000000..da88bc6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/DesugaredLibraryArtProfileRewritingTest.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2022, 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.profile.art;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DesugaredLibraryArtProfileRewritingTest extends DesugaredLibraryTestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public CompilationSpecification compilationSpecification;
+
+  @Parameter(2)
+  public LibraryDesugaringSpecification libraryDesugaringSpecification;
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        DEFAULT_SPECIFICATIONS,
+        getJdk8Jdk11());
+  }
+
+  @Test
+  public void test() throws Throwable {
+    Assume.assumeTrue(libraryDesugaringSpecification.hasEmulatedInterfaceDesugaring(parameters));
+    ArtProfileProviderForTesting artProfileProvider = new ArtProfileProviderForTesting();
+    ArtProfileConsumerForTesting residualArtProfileConsumer = new ArtProfileConsumerForTesting();
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addL8ArtProfileForRewriting(artProfileProvider, residualArtProfileConsumer)
+        .compile()
+        .inspectL8(
+            inspector -> {
+              ClassSubject consumerClassSubject =
+                  inspector.clazz(
+                      libraryDesugaringSpecification.functionPrefix(parameters)
+                          + ".util.function.Consumer");
+              assertThat(consumerClassSubject, isPresent());
+
+              ClassSubject streamClassSubject = inspector.clazz("j$.util.stream.Stream");
+              assertThat(streamClassSubject, isPresentAndNotRenamed());
+
+              MethodSubject forEachMethodSubject =
+                  streamClassSubject.uniqueMethodWithName("forEach");
+              assertThat(
+                  forEachMethodSubject,
+                  isPresentAndRenamed(
+                      compilationSpecification.isL8Shrink()
+                          && libraryDesugaringSpecification
+                              == LibraryDesugaringSpecification.JDK8));
+              assertEquals(
+                  consumerClassSubject.asTypeSubject(), forEachMethodSubject.getParameter(0));
+
+              assertEquals(
+                  Lists.newArrayList(forEachMethodSubject.getFinalReference()),
+                  residualArtProfileConsumer.references);
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("0");
+  }
+
+  class ArtProfileProviderForTesting implements ArtProfileProvider {
+
+    @Override
+    public void getArtProfile(ArtProfileBuilder profileBuilder) {
+      MethodReference forEachMethodReference =
+          Reference.method(
+              Reference.classFromTypeName("j$.util.stream.Stream"),
+              "forEach",
+              ImmutableList.of(
+                  Reference.classFromTypeName(
+                      libraryDesugaringSpecification.functionPrefix(parameters)
+                          + ".util.function.Consumer")),
+              null);
+      profileBuilder.addMethodRule(
+          methodRuleBuilder -> methodRuleBuilder.setMethodReference(forEachMethodReference));
+    }
+
+    @Override
+    public Origin getOrigin() {
+      return Origin.unknown();
+    }
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(new ArrayList<>().stream().collect(Collectors.toList()).size());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/diagnostic/HumanReadableArtProfileParserErrorDiagnosticFromArtProfileTest.java b/src/test/java/com/android/tools/r8/profile/art/diagnostic/HumanReadableArtProfileParserErrorDiagnosticFromArtProfileTest.java
new file mode 100644
index 0000000..ba3a623
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/diagnostic/HumanReadableArtProfileParserErrorDiagnosticFromArtProfileTest.java
@@ -0,0 +1,130 @@
+// Copyright (c) 2022, 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.profile.art.diagnostic;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.equalTo;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.profile.art.ArtProfileBuilder;
+import com.android.tools.r8.profile.art.ArtProfileClassRuleInfo;
+import com.android.tools.r8.profile.art.ArtProfileConsumer;
+import com.android.tools.r8.profile.art.ArtProfileMethodRuleInfo;
+import com.android.tools.r8.profile.art.ArtProfileProvider;
+import com.android.tools.r8.profile.art.ArtProfileRuleConsumer;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ConsumerUtils;
+import com.android.tools.r8.utils.UTF8TextInputStream;
+import java.io.ByteArrayInputStream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class HumanReadableArtProfileParserErrorDiagnosticFromArtProfileTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testD8() throws Exception {
+    testForD8()
+        .addProgramClasses(Main.class)
+        .addArtProfileForRewriting(createArtProfileProvider(), createArtProfileConsumer())
+        .release()
+        .setMinApi(AndroidApiLevel.LATEST)
+        .compileWithExpectedDiagnostics(this::inspectDiagnostics);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testR8() throws Exception {
+    testForR8(Backend.DEX)
+        .addProgramClasses(Main.class)
+        .addKeepMainRule(Main.class)
+        .addArtProfileForRewriting(createArtProfileProvider(), createArtProfileConsumer())
+        .release()
+        .setMinApi(AndroidApiLevel.LATEST)
+        .compileWithExpectedDiagnostics(this::inspectDiagnostics);
+  }
+
+  private ArtProfileProvider createArtProfileProvider() {
+    return new ArtProfileProvider() {
+
+      @Override
+      public void getArtProfile(ArtProfileBuilder artProfileBuilder) {
+        artProfileBuilder.addHumanReadableArtProfile(
+            new UTF8TextInputStream(new ByteArrayInputStream("INVALID1\nINVALID2".getBytes())),
+            ConsumerUtils.emptyConsumer());
+      }
+
+      @Override
+      public Origin getOrigin() {
+        return Origin.unknown();
+      }
+    };
+  }
+
+  private ArtProfileConsumer createArtProfileConsumer() {
+    return new ArtProfileConsumer() {
+
+      @Override
+      public ArtProfileRuleConsumer getRuleConsumer() {
+        return new ArtProfileRuleConsumer() {
+
+          @Override
+          public void acceptClassRule(
+              ClassReference classReference, ArtProfileClassRuleInfo classRuleInfo) {
+            // Ignore.
+          }
+
+          @Override
+          public void acceptMethodRule(
+              MethodReference methodReference, ArtProfileMethodRuleInfo methodRuleInfo) {
+            // Ignore.
+          }
+        };
+      }
+
+      @Override
+      public void finished(DiagnosticsHandler handler) {
+        // Ignore.
+      }
+    };
+  }
+
+  private void inspectDiagnostics(TestDiagnosticMessages diagnostics) {
+    diagnostics.assertErrorsMatch(
+        allOf(
+            diagnosticType(HumanReadableArtProfileParserErrorDiagnostic.class),
+            diagnosticMessage(
+                equalTo("Unable to parse rule at line 1 from ART profile: INVALID1"))),
+        allOf(
+            diagnosticType(HumanReadableArtProfileParserErrorDiagnostic.class),
+            diagnosticMessage(
+                equalTo("Unable to parse rule at line 2 from ART profile: INVALID2"))));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/diagnostic/HumanReadableArtProfileParserErrorDiagnosticTest.java b/src/test/java/com/android/tools/r8/profile/art/diagnostic/HumanReadableArtProfileParserErrorDiagnosticFromStartupProfileTest.java
similarity index 96%
rename from src/test/java/com/android/tools/r8/profile/art/diagnostic/HumanReadableArtProfileParserErrorDiagnosticTest.java
rename to src/test/java/com/android/tools/r8/profile/art/diagnostic/HumanReadableArtProfileParserErrorDiagnosticFromStartupProfileTest.java
index aee021b..8ee4777 100644
--- a/src/test/java/com/android/tools/r8/profile/art/diagnostic/HumanReadableArtProfileParserErrorDiagnosticTest.java
+++ b/src/test/java/com/android/tools/r8/profile/art/diagnostic/HumanReadableArtProfileParserErrorDiagnosticFromStartupProfileTest.java
@@ -28,7 +28,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class HumanReadableArtProfileParserErrorDiagnosticTest extends TestBase {
+public class HumanReadableArtProfileParserErrorDiagnosticFromStartupProfileTest extends TestBase {
 
   @Parameter(0)
   public TestParameters parameters;
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertionerror/AssertionErrorRewriteApi16Test.java b/src/test/java/com/android/tools/r8/rewrite/assertionerror/AssertionErrorRewriteApi16Test.java
index 4fbb7c3..e8ca0e3 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertionerror/AssertionErrorRewriteApi16Test.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertionerror/AssertionErrorRewriteApi16Test.java
@@ -44,6 +44,6 @@
         .enableInliningAnnotations()
         .setMinApi(AndroidApiLevel.J)
         .run(parameters.getRuntime(), Main.class, String.valueOf(false))
-        .assertSuccessWithOutputLines("OK", "OK");
+        .assertSuccessWithOutputLines("message", "java.lang.RuntimeException: cause message");
   }
 }
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertionerror/AssertionErrorRewriteTest.java b/src/test/java/com/android/tools/r8/rewrite/assertionerror/AssertionErrorRewriteTest.java
index 2318414..f22208c 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertionerror/AssertionErrorRewriteTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertionerror/AssertionErrorRewriteTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.rewrite.assertionerror;
 
-import static com.android.tools.r8.ToolHelper.getDefaultAndroidJar;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.NeverInline;
@@ -38,46 +37,33 @@
   @Test public void d8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
     testForD8()
-        .addLibraryFiles(getDefaultAndroidJar())
         .addProgramClasses(Main.class)
         .setMinApi(parameters.getApiLevel())
-        .run(parameters.getRuntime(), Main.class, String.valueOf(expectCause))
-        .assertSuccessWithOutputLines("OK", "OK");
+        .run(parameters.getRuntime(), Main.class)
+        // None of the VMs we have for testing is missing the two args constructor.
+        .assertSuccessWithOutputLines("message", "java.lang.RuntimeException: cause message");
   }
 
   @Test public void r8() throws Exception {
     testForR8(parameters.getBackend())
-        .addLibraryFiles(getDefaultAndroidJar())
         .addProgramClasses(Main.class)
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class, String.valueOf(expectCause))
-        .assertSuccessWithOutputLines("OK", "OK");
+        // None of the VMs we have for testing is missing the two args constructor.
+        .assertSuccessWithOutputLines("message", "java.lang.RuntimeException: cause message");
   }
 
   public static final class Main {
     public static void main(String[] args) {
-      boolean expectCause = Boolean.parseBoolean(args[0]);
-
       Throwable expectedCause = new RuntimeException("cause message");
       try {
         throwAssertionError(expectedCause);
         System.out.println("unreachable");
       } catch (AssertionError e) {
-        String message = e.getMessage();
-        if (!message.equals("message")) {
-          throw new RuntimeException("Incorrect AssertionError message: " + message);
-        } else {
-          System.out.println("OK");
-        }
-
-        Throwable cause = e.getCause();
-        if (expectCause && cause != expectedCause) {
-          throw new RuntimeException("Incorrect AssertionError cause", cause);
-        } else {
-          System.out.println("OK");
-        }
+        System.out.println(e.getMessage());
+        System.out.println(e.getCause());
       }
     }
 
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
index 0cf3e9e..7fc0289 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
@@ -51,7 +51,6 @@
     options.enableEnumValueOptimization = enableOptimization;
     options.enableEnumSwitchMapRemoval = enableOptimization;
     options.enableEnumUnboxing = false;
-    options.apiModelingOptions().enableApiCallerIdentification = true;
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/ForwardingConstructorShakingOnDexTest.java b/src/test/java/com/android/tools/r8/shaking/ForwardingConstructorShakingOnDexTest.java
new file mode 100644
index 0000000..6ff7000
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ForwardingConstructorShakingOnDexTest.java
@@ -0,0 +1,133 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.KeepConstantArguments;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ForwardingConstructorShakingOnDexTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.testing.enableRedundantConstructorBridgeRemoval = true)
+        .enableConstantArgumentAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A", "A(String)", "B", "B(String)", "C", "C(String)");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    boolean canHaveNonReboundConstructorInvoke =
+        parameters.isDexRuntime()
+            && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L);
+
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+    assertEquals(2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+
+    ClassSubject bClassSubject = inspector.clazz(B.class);
+    assertThat(bClassSubject, isPresent());
+    assertEquals(
+        canHaveNonReboundConstructorInvoke ? 0 : 2,
+        bClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+
+    ClassSubject cClassSubject = inspector.clazz(C.class);
+    assertThat(cClassSubject, isPresent());
+    assertEquals(
+        canHaveNonReboundConstructorInvoke ? 0 : 2,
+        cClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new A();
+      new A("A(String)");
+      new B();
+      new B("B(String)");
+      new C();
+      new C("C(String)");
+    }
+  }
+
+  @NeverClassInline
+  @NoVerticalClassMerging
+  static class A {
+
+    A() {
+      if (this instanceof C) {
+        System.out.println("C");
+      } else if (this instanceof B) {
+        System.out.println("B");
+      } else {
+        System.out.println("A");
+      }
+    }
+
+    A(String string) {
+      System.out.println(string);
+    }
+  }
+
+  @NeverClassInline
+  @NoVerticalClassMerging
+  static class B extends A {
+
+    // These constructors simply forward the arguments to the parent constructor.
+    // They can be removed when compiling for dex and the API is above Dalvik.
+    B() {}
+
+    B(String string) {
+      super(string);
+    }
+  }
+
+  @NeverClassInline
+  @NoVerticalClassMerging
+  static class C extends B {
+
+    // Ditto.
+    C() {}
+
+    @KeepConstantArguments
+    C(String string) {
+      super(string);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ForwardingConstructorShakingOnDexWithClassMergingTest.java b/src/test/java/com/android/tools/r8/shaking/ForwardingConstructorShakingOnDexWithClassMergingTest.java
new file mode 100644
index 0000000..c7f9a92
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ForwardingConstructorShakingOnDexWithClassMergingTest.java
@@ -0,0 +1,144 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.Iterables;
+import java.util.Objects;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ForwardingConstructorShakingOnDexWithClassMergingTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> {
+              options.testing.enableRedundantConstructorBridgeRemoval = true;
+              options.testing.horizontalClassMergingTarget =
+                  (appView, candidates, target) ->
+                      Iterables.find(
+                          candidates,
+                          candidate -> candidate.getTypeName().equals(B.class.getTypeName()));
+            })
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertMergedInto(A.class, B.class).assertNoOtherClassesMerged())
+        .enableInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  private boolean canHaveNonReboundConstructorInvoke() {
+    return parameters.isDexRuntime()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject aSubClassSubject = inspector.clazz(ASub.class);
+    assertThat(aSubClassSubject, isPresent());
+    assertEquals(
+        canHaveNonReboundConstructorInvoke() ? 0 : 1, aSubClassSubject.allMethods().size());
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      System.out.print(new A(obfuscate("Hello")).getMessageFromA());
+      System.out.print(new ASub(obfuscate(", ")).getMessageFromA());
+      System.out.println(new B(obfuscate("world!")).getMessageFromB());
+    }
+
+    @NeverInline
+    private static String obfuscate(String str) {
+      return System.currentTimeMillis() > 0 ? str : "dead";
+    }
+
+    @NeverInline
+    public static void requireNonNull(Object o) {
+      Objects.requireNonNull(o);
+    }
+  }
+
+  @NoVerticalClassMerging
+  public static class A {
+
+    private final String msg;
+
+    // Will be merged with B.<init>(String). Since B is chosen as the merge target, a new
+    // B.<init>(String, int) method will be generated, and mappings will be created from
+    // A.<init>(String) -> B.init$A(String) and B.<init>(String) -> B.init$B(String).
+    public A(String msg) {
+      // So that A.<init>(String) and B.<init>(String) will not be considered identical during class
+      // merging. The indirection is to ensure that the API level of class A is 1.
+      Main.requireNonNull(msg);
+      this.msg = msg;
+    }
+
+    // Will be moved to B.<init>(String, String) by horizontal class merging, and then be optimized
+    // to B.<init>(String) by unused argument removal.
+    public A(String unused, String msg) {
+      this.msg = msg;
+    }
+
+    @NeverInline
+    public String getMessageFromA() {
+      return msg;
+    }
+  }
+
+  public static class ASub extends A {
+
+    // After horizontal class merging and unused argument removal, this is rewritten to target
+    // B.<init>(String). This constructor can therefore be eliminated by the redundant bridge
+    // removal phase.
+    public ASub(String msg) {
+      super("<unused>", msg);
+    }
+  }
+
+  public static class B {
+
+    private final String msg;
+
+    public B(String msg) {
+      this.msg = msg;
+    }
+
+    @NeverInline
+    public String getMessageFromB() {
+      return msg;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/RetainIndirectlyReferencedConstructorShakingOnDexTest.java b/src/test/java/com/android/tools/r8/shaking/RetainIndirectlyReferencedConstructorShakingOnDexTest.java
new file mode 100644
index 0000000..f914a87
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/RetainIndirectlyReferencedConstructorShakingOnDexTest.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RetainIndirectlyReferencedConstructorShakingOnDexTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.testing.enableRedundantConstructorBridgeRemoval = true)
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A", "B");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    boolean canHaveNonReboundConstructorInvoke =
+        parameters.isDexRuntime()
+            && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L);
+
+    // A.<init> should be retained despite the fact that there is no invoke-direct in the program
+    // that directly targets A.<init> when B.<init> is removed.
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+    assertEquals(1, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+
+    ClassSubject bClassSubject = inspector.clazz(B.class);
+    assertThat(bClassSubject, isPresent());
+    assertEquals(
+        canHaveNonReboundConstructorInvoke ? 0 : 1,
+        bClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      // This uses an invoke-direct instruction that targets B.<init>(). If we remove B.<init>()
+      // when compiling for dex, and keep the invoke-direct instruction targeting B.<init>(), it is
+      // important that tree shaking traces A.<init>(), by resolving the invoke in the Enqueuer,
+      // instead of trying to lookup <init>() directly on B. Otherwise, the program will fail with
+      // NSME.
+      System.out.println(new B());
+    }
+  }
+
+  @NoVerticalClassMerging
+  abstract static class A {
+
+    A() {
+      System.out.println("A");
+    }
+  }
+
+  @NoVerticalClassMerging
+  static class B extends A {
+
+    // This constructor simply forward the arguments to the parent constructor.
+    // It can be removed when compiling for dex and the API is above Dalvik.
+    B() {}
+
+    @Override
+    public String toString() {
+      return "B";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/convertchecknotnull/ConvertCheckNotNullTest.java b/src/test/java/com/android/tools/r8/shaking/convertchecknotnull/ConvertCheckNotNullTest.java
index 5aad9aa..27f7735 100644
--- a/src/test/java/com/android/tools/r8/shaking/convertchecknotnull/ConvertCheckNotNullTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/convertchecknotnull/ConvertCheckNotNullTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+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.CodeMatchers;
@@ -64,7 +65,10 @@
               MethodSubject mainMethodSubject = mainClassSubject.mainMethod();
               assertThat(mainMethodSubject, isPresent());
               assertEquals(
-                  6,
+                  parameters.isDexRuntime()
+                          && parameters.getApiLevel().isLessThan(AndroidApiLevel.K)
+                      ? 4
+                      : 6,
                   mainMethodSubject
                       .streamInstructions()
                       .filter(
@@ -92,8 +96,16 @@
       }
     } else {
       if (parameters.getDexRuntimeVersion().isEqualToOneOf(Version.V8_1_0, Version.DEFAULT)) {
-        message4 =
-            message5 = message6 = "Attempt to invoke a virtual method on a null object reference";
+        if (parameters.getApiLevel().isLessThan(AndroidApiLevel.K)) {
+          message4 = message5 = "Attempt to invoke a virtual method on a null object reference";
+          message6 =
+              "Attempt to invoke virtual method 'java.lang.Class"
+                  + " java.lang.Object.getClass()' on a null object reference";
+
+        } else {
+          message4 =
+              message5 = message6 = "Attempt to invoke a virtual method on a null object reference";
+        }
       } else if (parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V5_1_1)) {
         message4 =
             message5 =
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index da3ef50..9b534e2 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -46,6 +46,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.function.Consumer;
 import org.junit.Assert;
@@ -1384,7 +1385,14 @@
               opts.outline.maxSize = 3;
 
               // Do not allow dead code elimination of the new-instance instructions.
-              opts.apiModelingOptions().enableApiCallerIdentification = false;
+              opts.apiModelingOptions().disableApiModeling();
+              // Do not allow dead code elimination of the new-instance instructions. This can be
+              // achieved by not assuming that StringBuilder is present.
+              DexItemFactory dexItemFactory = opts.itemFactory;
+              opts.itemFactory.libraryTypesAssumedToBePresent =
+                  new HashSet<>(dexItemFactory.libraryTypesAssumedToBePresent);
+              dexItemFactory.libraryTypesAssumedToBePresent.remove(
+                  dexItemFactory.stringBuilderType);
             });
 
     AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
diff --git a/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
index 691a33d..b6e08bf 100644
--- a/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
+++ b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.startup.instrumentation.StartupInstrumentationOptions;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.profile.art.AlwaysTrueArtProfileRulePredicate;
 import com.android.tools.r8.profile.art.ArtProfileBuilder;
 import com.android.tools.r8.profile.art.ArtProfileBuilderUtils;
 import com.android.tools.r8.profile.art.ArtProfileBuilderUtils.SyntheticToSyntheticContextGeneralization;
@@ -86,12 +85,12 @@
           public StartupProfileBuilder addHumanReadableArtProfile(
               TextInputStream textInputStream,
               Consumer<HumanReadableArtProfileParserBuilder> parserBuilderConsumer) {
+            // The ART profile parser never calls addHumanReadableArtProfile().
             throw new Unreachable();
           }
         };
     return ArtProfileBuilderUtils.createBuilderForArtProfileToStartupProfileConversion(
         startupProfileBuilder,
-        new AlwaysTrueArtProfileRulePredicate(),
         syntheticToSyntheticContextGeneralization);
   }
 
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index e3ad2c8..c5bb96f 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -66,6 +66,10 @@
     return syntheticClass(clazz, naming.API_MODEL_OUTLINE, id);
   }
 
+  public static ClassReference syntheticApiOutlineClass(ClassReference classReference, int id) {
+    return syntheticClass(classReference, naming.API_MODEL_OUTLINE, id);
+  }
+
   public static ClassReference syntheticBackportClass(Class<?> clazz, int id) {
     return syntheticClass(clazz, naming.BACKPORT, id);
   }
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index b4c6e12..322c1d4 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -1007,6 +1007,26 @@
           }
 
           @Override
+          public void visitFrame(
+              int type, int numLocal, Object[] local, int numStack, Object[] stack) {
+            for (int i = 0; i < numLocal; i++) {
+              Object object = local[i];
+              if (object instanceof String) {
+                local[i] = rewriteASMInternalTypeName((String) object);
+              }
+              i++;
+            }
+            for (int i = 0; i < numStack; i++) {
+              Object object = stack[i];
+              if (object instanceof String) {
+                stack[i] = rewriteASMInternalTypeName((String) object);
+              }
+              i++;
+            }
+            super.visitFrame(type, numLocal, local, numStack, stack);
+          }
+
+          @Override
           public void visitLdcInsn(Object value) {
             if (value instanceof Type) {
               Type type = (Type) value;
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 e3855b0..aeeb8f4 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
@@ -12,6 +12,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.references.MethodReference;
 import java.util.List;
 
 public class AbsentMethodSubject extends MethodSubject {
@@ -82,6 +83,11 @@
   }
 
   @Override
+  public MethodReference getFinalReference() {
+    throw new Unreachable("Cannot get the final reference for an absent method");
+  }
+
+  @Override
   public TypeSubject getParameter(int index) {
     throw new Unreachable("Cannot get the parameter for an absent method");
   }
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 533fa55..3e28a56 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
@@ -134,6 +134,11 @@
   }
 
   @Override
+  public MethodReference getFinalReference() {
+    return dexMethod.getReference().asMethodReference();
+  }
+
+  @Override
   public TypeSubject getParameter(int index) {
     return new TypeSubject(codeInspector, getMethod().getParameter(index));
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
index 69b8232..91a9a80 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
@@ -87,6 +87,11 @@
   }
 
   public HorizontallyMergedClassesInspector assertMergedInto(Class<?> from, Class<?> target) {
+    return assertMergedInto(Reference.classFromClass(from), Reference.classFromClass(target));
+  }
+
+  public HorizontallyMergedClassesInspector assertMergedInto(
+      ClassReference from, ClassReference target) {
     assertEquals(
         horizontallyMergedClasses.getMergeTargetOrDefault(toDexType(from)), toDexType(target));
     seen.add(toDexType(from).asClassReference());
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 a083636..ccc63b0 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
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.references.MethodReference;
 import com.google.common.collect.Streams;
 import java.util.Iterator;
 import java.util.List;
@@ -54,6 +55,8 @@
 
   public abstract DexEncodedMethod getMethod();
 
+  public abstract MethodReference getFinalReference();
+
   public abstract TypeSubject getParameter(int index);
 
   public abstract List<TypeSubject> getParameters();
diff --git a/third_party/api_database/api_database.tar.gz.sha1 b/third_party/api_database/api_database.tar.gz.sha1
index 3a106aa..275471b 100644
--- a/third_party/api_database/api_database.tar.gz.sha1
+++ b/third_party/api_database/api_database.tar.gz.sha1
@@ -1 +1 @@
-da5a2b797563b19461f62db48bf1e45e2f86752f
\ No newline at end of file
+f081c538df68649432fa8e45ec511d43d5548396
\ No newline at end of file
diff --git a/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1 b/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
index 6d54f29..524abea 100644
--- a/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
+++ b/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
@@ -1 +1 @@
-cbd86c3d046d227e71e8157742b3a1f2e4abcd8e
\ No newline at end of file
+ae103f657ce219b839706397baac799745d46049
\ No newline at end of file
diff --git a/tools/archive_desugar_jdk_libs.py b/tools/archive_desugar_jdk_libs.py
index 004450b..63d02c6 100755
--- a/tools/archive_desugar_jdk_libs.py
+++ b/tools/archive_desugar_jdk_libs.py
@@ -75,6 +75,9 @@
   'jdk11_nio': BASE_LIBRARY_NAME + '_jdk11_nio.zip'
 }
 
+DESUGAR_JDK_LIBS_HASH_FILE = os.path.join(
+    defines.THIRD_PARTY, 'openjdk', 'desugar_jdk_libs_11', 'desugar_jdk_libs_hash')
+
 
 def ParseOptions(argv):
   result = optparse.OptionParser()
@@ -131,11 +134,11 @@
     print('File available at: %s' %
         destination.replace('gs://', 'https://storage.googleapis.com/', 1))
 
-def CloneDesugaredLibrary(github_account, checkout_dir):
+def CloneDesugaredLibrary(github_account, checkout_dir, desugar_jdk_libs_hash):
   git_utils.GitClone(
     'https://github.com/'
         + github_account + '/' + GITHUB_REPRO, checkout_dir)
-  git_utils.GitCheckout('3a970cd008e944845a7b3d29a3b5a13123df11fe', checkout_dir)
+  git_utils.GitCheckout(desugar_jdk_libs_hash, checkout_dir)
 
 def GetJavaEnv():
   java_env = dict(os.environ, JAVA_HOME = jdk.GetJdk11Home())
@@ -251,10 +254,13 @@
     raise Exception(path + ' does not exist or is not a directory')
 
 def BuildAndUpload(options, variant):
+  desugar_jdk_libs_hash = ''
+  with open(DESUGAR_JDK_LIBS_HASH_FILE, 'r') as input_hash:
+    desugar_jdk_libs_hash = input_hash.readline()
   if options.build_only:
     with utils.TempDir() as checkout_dir:
-      CloneDesugaredLibrary(options.github_account, checkout_dir)
-      (library_jar, maven_zip) = BuildDesugaredLibrary(checkout_dir, variant)
+      CloneDesugaredLibrary(options.github_account, checkout_dir, desugar_jdk_libs_hash)
+      (library_jar, maven_zip) = BuildDesugaredLibrary(checkout_dir, variant, desugar_jdk_libs_hash)
       shutil.copyfile(
         library_jar,
         os.path.join(options.build_only, os.path.basename(library_jar)))
@@ -267,7 +273,7 @@
   is_main = False
 
   with utils.TempDir() as checkout_dir:
-    CloneDesugaredLibrary(options.github_account, checkout_dir)
+    CloneDesugaredLibrary(options.github_account, checkout_dir, desugar_jdk_libs_hash)
     version = GetVersion(os.path.join(checkout_dir, VERSION_MAP[variant]))
 
     destination = archive.GetVersionDestination(
@@ -318,6 +324,7 @@
   utils.DownloadFromGoogleCloudStorage(utils.BAZEL_SHA_FILE)
   utils.DownloadFromGoogleCloudStorage(utils.JAVA8_SHA_FILE)
   utils.DownloadFromGoogleCloudStorage(utils.JAVA11_SHA_FILE)
+  utils.DownloadFromGoogleCloudStorage(utils.DESUGAR_JDK_LIBS_11_SHA_FILE)
 
   for v in options.variant:
     BuildAndUpload(options, v)
diff --git a/tools/cherry-pick.py b/tools/cherry-pick.py
index 08fdac3..a51ed00 100755
--- a/tools/cherry-pick.py
+++ b/tools/cherry-pick.py
@@ -9,15 +9,18 @@
 import sys
 import utils
 
+VERSION_FILE = 'src/main/java/com/android/tools/r8/Version.java'
+VERSION_PREFIX = 'String LABEL = "'
+
 def parse_options():
   parser = argparse.ArgumentParser(description='Release r8')
   parser.add_argument('--branch',
                       metavar=('<branch>'),
                       help='Branch to cherry-pick to')
-  parser.add_argument('--clean-checkout', '--clean_checkout',
+  parser.add_argument('--current-checkout', '--current_checkout',
                       default=False,
                       action='store_true',
-                      help='Perform cherry picks in a clean checkout')
+                      help='Perform cherry picks into the current checkout')
   parser.add_argument('--no-upload', '--no_upload',
                       default=False,
                       action='store_true',
@@ -32,12 +35,14 @@
   # Checkout the branch.
   subprocess.check_output(['git', 'checkout', args.branch])
 
-  if (not args.clean_checkout):
+  if (args.current_checkout):
     for i in range(len(args.hashes) + 1):
       branch = 'cherry-%d' % (i + 1)
       print('Deleting branch %s' % branch)
       subprocess.run(['git', 'branch', branch, '-D'])
 
+  bugs = set()
+
   count = 1
   for hash in args.hashes:
     branch = 'cherry-%d' % count
@@ -49,39 +54,67 @@
 
     subprocess.run(['git', 'cherry-pick', hash])
 
-    question = ('Ready to continue (cwd %s, will not upload to Gerrit)' % os.getcwd()
-      if args.no_upload else
-      'Ready to upload %s (cwd %s)' % (branch, os.getcwd()))
-    answer = input(question + ' [y/N]?')
-    if answer != 'y':
-      print('Aborting new branch for %s' % branch_version)
-      sys.exit(1)
-
-    if (not args.no_upload):
-      subprocess.run(['git', 'cl', 'upload'])
+    commit_message = subprocess.check_output(['git', 'log', '--format=%B', '-n', '1', 'HEAD'])
+    commit_lines = [l.strip() for l in commit_message.decode('UTF-8').split('\n')]
+    for line in commit_lines:
+      if line.startswith('Bug: '):
+        normalized = line.replace('Bug: ', '').replace('b/', '')
+        if len(normalized) > 0:
+          bugs.add(normalized)
+    confirm_and_upload(branch, args)
     count = count + 1
 
   branch = 'cherry-%d' % count
   subprocess.run(['git', 'new-branch', branch, '--upstream-current'])
-  editor = os.environ.get('VISUAL')
-  if not editor:
-    editor = os.environ.get('EDITOR')
-  if not editor:
-    editor = 'vi'
+
+  old_version = 'unknown'
+  for line in open(VERSION_FILE, 'r'):
+    index = line.find(VERSION_PREFIX)
+    if index > 0:
+      index += len(VERSION_PREFIX)
+      subline = line[index:]
+      old_version = subline[:subline.index('"')]
+      break
+
+  new_version = 'unknown'
+  if old_version.find('.') > 0:
+    split_version = old_version.split('.')
+    new_version = '.'.join(split_version[:-1] + [str(int(split_version[-1]) + 1)])
+    subprocess.run(['sed', '-i', 's/%s/%s/' % (old_version, new_version), VERSION_FILE])
   else:
-    print("Opening src/main/java/com/android/tools/r8/Version.java" +
-      " for version update with %" % editor)
-  subprocess.run([editor, 'src/main/java/com/android/tools/r8/Version.java'])
-  subprocess.run(['git', 'commit', '-a'])
-  if (not args.no_upload):
-    subprocess.run(['git', 'cl', 'upload'])
-  if (args.clean_checkout):
+    editor = os.environ.get('VISUAL')
+    if not editor:
+      editor = os.environ.get('EDITOR')
+    if not editor:
+      editor = 'vi'
+    else:
+      print("Opening %s for version update with %s" % (VERSION_FILE, editor))
+      subprocess.run([editor, VERSION_FILE])
+
+  message = ("Version %s\n\n" % new_version)
+  for bug in sorted(bugs):
+    message += 'Bug: b/%s\n' % bug
+
+  subprocess.run(['git', 'commit', '-a', '-m', message])
+  confirm_and_upload(branch, args)
+  if (not args.current_checkout):
     answer = input('Done, press enter to delete checkout in %s' % os.getcwd())
 
+def confirm_and_upload(branch, args):
+  question = ('Ready to continue (cwd %s, will not upload to Gerrit)' % os.getcwd()
+    if args.no_upload else
+        'Ready to upload %s (cwd %s)' % (branch, os.getcwd()))
+  answer = input(question + ' [y/N]?')
+  if answer != 'y':
+    print('Aborting new branch for %s' % branch_version)
+    sys.exit(1)
+  if (not args.no_upload):
+    subprocess.run(['git', 'cl', 'upload', '--bypass-hooks'])
+
 def main():
   args = parse_options()
 
-  if (args.clean_checkout):
+  if (not args.current_checkout):
     with utils.TempDir() as temp:
       print("Performing cherry-picking in %s" % temp)
       subprocess.check_call(['git', 'clone', utils.REPO_SOURCE, temp])
diff --git a/tools/desugar_jdk_libs_repository.py b/tools/desugar_jdk_libs_repository.py
index 7cb7a19..08cb6c1 100755
--- a/tools/desugar_jdk_libs_repository.py
+++ b/tools/desugar_jdk_libs_repository.py
@@ -10,6 +10,7 @@
 import shutil
 import subprocess
 import sys
+import urllib.request
 
 import gradle
 import utils
@@ -42,6 +43,13 @@
                       default=None,
                       metavar=('<path>'),
                       help='Use existing checkout of github.com/google/desugar_jdk_libs.')
+  parser.add_argument('--desugar-jdk-libs-revision', '--desugar_jdk_libs_revision',
+                      default=None,
+                      metavar=('<revision>'),
+                      help='Revision of github.com/google/desugar_jdk_libs to use.')
+  parser.add_argument('--release-version', '--release_version',
+                      metavar=('<version>'),
+                      help='The desugared library release version to use. This will pull from the archived releases')
   args = parser.parse_args()
   return args
 
@@ -69,7 +77,8 @@
   implementation = None
   version_file = None
   implementation_build_target = None
-  implementation_build_output = None
+  implementation_maven_zip = None
+  release_archive_location = None
   match args.variant:
     case Variant.jdk8:
       artifact = 'desugar_jdk_libs'
@@ -79,7 +88,8 @@
       implementation = utils.DESUGAR_IMPLEMENTATION
       version_file = 'VERSION.txt'
       implementation_build_target = ':maven_release'
-      implementation_build_output = join('bazel-bin', 'desugar_jdk_libs.zip')
+      implementation_maven_zip = 'desugar_jdk_libs.zip'
+      release_archive_location = 'desugar_jdk_libs'
     case Variant.jdk11_legacy:
       artifact = 'desugar_jdk_libs'
       configuration_artifact = 'desugar_jdk_libs_configuration'
@@ -88,7 +98,8 @@
       implementation = utils.DESUGAR_IMPLEMENTATION_JDK11
       version_file = 'VERSION_JDK11_LEGACY.txt'
       implementation_build_target = ':maven_release_jdk11_legacy'
-      implementation_build_output = join('bazel-bin', 'desugar_jdk_libs_jdk11_legacy.zip')
+      implementation_maven_zip = 'desugar_jdk_libs_jdk11_legacy.zip'
+      release_archive_location = 'desugar_jdk_libs'
     case Variant.jdk11_minimal:
       artifact = 'desugar_jdk_libs_minimal'
       configuration_artifact = 'desugar_jdk_libs_configuration_minimal'
@@ -97,7 +108,8 @@
       implementation = utils.DESUGAR_IMPLEMENTATION_JDK11
       version_file = 'VERSION_JDK11_MINIMAL.txt'
       implementation_build_target = ':maven_release_jdk11_minimal'
-      implementation_build_output = join('bazel-bin', 'desugar_jdk_libs_jdk11_minimal.zip')
+      implementation_maven_zip = 'desugar_jdk_libs_jdk11_minimal.zip'
+      release_archive_location = 'desugar_jdk_libs_minimal'
     case Variant.jdk11:
       artifact = 'desugar_jdk_libs'
       configuration_artifact = 'desugar_jdk_libs_configuration'
@@ -106,7 +118,8 @@
       implementation = utils.DESUGAR_IMPLEMENTATION_JDK11
       version_file = 'VERSION_JDK11.txt'
       implementation_build_target = ':maven_release_jdk11'
-      implementation_build_output = join('bazel-bin', 'desugar_jdk_libs_jdk11.zip')
+      implementation_maven_zip = 'desugar_jdk_libs_jdk11.zip'
+      release_archive_location = 'desugar_jdk_libs'
     case Variant.jdk11_nio:
       artifact = 'desugar_jdk_libs_nio'
       configuration_artifact = 'desugar_jdk_libs_configuration_nio'
@@ -115,30 +128,43 @@
       implementation = utils.DESUGAR_IMPLEMENTATION_JDK11
       version_file = 'VERSION_JDK11_NIO.txt'
       implementation_build_target = ':maven_release_jdk11_nio'
-      implementation_build_output = join('bazel-bin', 'desugar_jdk_libs_jdk11_nio.zip')
+      implementation_maven_zip = 'desugar_jdk_libs_jdk11_nio.zip'
+      release_archive_location = 'desugar_jdk_libs_nio'
+  implementation_build_output = join('bazel-bin', implementation_maven_zip)
   gradle.RunGradle([utils.R8])
-  with utils.TempDir(delete=False) as tmp_dir:
-    (name, version) = utils.desugar_configuration_name_and_version(configuration, False)
+  with utils.TempDir() as tmp_dir:
+    (name, configuration_version) = utils.desugar_configuration_name_and_version(configuration, False)
+    if (args.release_version != None and args.release_version != configuration_version):
+      raise Exception(
+          'Configuration version %s is different for specified version %s'
+          % (configuration_version, version))
+    version = configuration_version
+    print("Name: %s" % name)
+    print("Version: %s" % version)
     # Checkout desugar_jdk_libs from GitHub
     use_existing_checkout = args.desugar_jdk_libs_checkout != None
     checkout_dir = (args.desugar_jdk_libs_checkout
                     if use_existing_checkout
                     else join(tmp_dir, 'desugar_jdk_libs'))
-    if (not use_existing_checkout):
-      subprocess.check_call(['git', 'clone', 'https://github.com/google/desugar_jdk_libs.git', checkout_dir])
-    with utils.ChangedWorkingDirectory(checkout_dir):
-      with open(version_file) as version_file:
-        version_file_lines = version_file.readlines()
-        for line in version_file_lines:
-          if not line.startswith('#'):
-            desugar_jdk_libs_version = line.strip()
-            if (version != desugar_jdk_libs_version):
-              raise Exception(
-                "Version mismatch. Configuration has version '"
-                + version
-                + "', and desugar_jdk_libs has version '"
-                + desugar_jdk_libs_version
-                + "'")
+    if (not args.release_version and not use_existing_checkout):
+      subprocess.check_call(
+          ['git', 'clone', 'https://github.com/google/desugar_jdk_libs.git', checkout_dir])
+      if (args.desugar_jdk_libs_revision):
+        subprocess.check_call(
+            ['git', '-C', checkout_dir, 'checkout', args.desugar_jdk_libs_revision])
+      with utils.ChangedWorkingDirectory(checkout_dir):
+        with open(version_file) as version_file:
+          version_file_lines = version_file.readlines()
+          for line in version_file_lines:
+            if not line.startswith('#'):
+              desugar_jdk_libs_version = line.strip()
+              if (version != desugar_jdk_libs_version):
+                raise Exception(
+                  "Version mismatch. Configuration has version '"
+                  + version
+                  + "', and desugar_jdk_libs has version '"
+                  + desugar_jdk_libs_version
+                  + "'")
 
     # Build desugared library configuration.
     print("Building desugared library configuration " + version)
@@ -160,27 +186,36 @@
       '-DpomFile=' + pom_file(unzip_dir, configuration_artifact, version)]
     subprocess.check_call(cmd)
 
-    # Build desugared library.
-    print("Building desugared library " + version)
-    with utils.ChangedWorkingDirectory(checkout_dir):
-      subprocess.check_call([
-          'bazel',
-          '--bazelrc=/dev/null',
-          'build',
-          '--spawn_strategy=local',
-          '--verbose_failures',
-          implementation_build_target])
+    undesugared_if_needed = None
+    if not args.release_version:
+      # Build desugared library.
+      print("Building desugared library " + version)
+      with utils.ChangedWorkingDirectory(checkout_dir):
+        subprocess.check_call([
+            'bazel',
+            '--bazelrc=/dev/null',
+            'build',
+            '--spawn_strategy=local',
+            '--verbose_failures',
+            implementation_build_target])
 
-    # Undesugar desugared library if needed.
-    undesugared_if_needed = join(checkout_dir, implementation_build_output)
-    if (args.variant == Variant.jdk11_minimal
-        or args.variant == Variant.jdk11
-        or args.variant == Variant.jdk11_nio):
-      undesugared_if_needed = join(tmp_dir, 'undesugared.zip')
-      archive_desugar_jdk_libs.Undesugar(
-        str(args.variant),
-        join(checkout_dir, implementation_build_output),
-        version,
+      # Undesugar desugared library if needed.
+      undesugared_if_needed = join(checkout_dir, implementation_build_output)
+      if (args.variant == Variant.jdk11_minimal
+          or args.variant == Variant.jdk11
+          or args.variant == Variant.jdk11_nio):
+        undesugared_if_needed = join(tmp_dir, 'undesugared.zip')
+        archive_desugar_jdk_libs.Undesugar(
+          str(args.variant),
+          join(checkout_dir, implementation_build_output),
+          version,
+          undesugared_if_needed)
+    else:
+      # Download the already built and undesugared library from release archive.
+      undesugared_if_needed = join(tmp_dir, implementation_maven_zip)
+      urllib.request.urlretrieve(
+        ('https://storage.googleapis.com/r8-releases/raw/%s/%s/%s'
+            % (release_archive_location, version, implementation_maven_zip)),
         undesugared_if_needed)
 
     unzip_dir = join(tmp_dir, 'desugar_jdk_libs_unzipped')
@@ -227,6 +262,15 @@
 
 def main():
   args = parse_options()
+  if args.desugar_jdk_libs_checkout and args.release_version:
+    raise Exception(
+        'Options --desugar-jdk-libs-checkout and --release-version are mutually exclusive')
+  if args.desugar_jdk_libs_revision and args.release_version:
+    raise Exception(
+        'Options --desugar-jdk-libs-revision and --release-version are mutually exclusive')
+  if args.desugar_jdk_libs_checkout and args.desugar_jdk_libs_revision:
+    raise Exception(
+        'Options --desugar-jdk-libs-checkout and --desugar-jdk-libs-revision are mutually exclusive')
   if args.clear_repo:
     shutil.rmtree(args.repo_root, ignore_errors=True)
   utils.makedirs_if_needed(args.repo_root)
diff --git a/tools/desugar_jdk_libs_update.py b/tools/desugar_jdk_libs_update.py
new file mode 100755
index 0000000..2e751e9
--- /dev/null
+++ b/tools/desugar_jdk_libs_update.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022, 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.
+
+import argparse
+import os
+from os.path import join
+import shutil
+import subprocess
+import sys
+
+import utils
+
+def GetGitHash(checkout_dir):
+  return subprocess.check_output(
+      ['git', '-C', checkout_dir, 'rev-parse', 'HEAD']).decode('utf-8').strip()
+
+def run(args):
+  with utils.TempDir() as tmp_dir:
+    use_existing_checkout = args.desugar_jdk_libs_checkout != None
+    checkout_dir = (args.desugar_jdk_libs_checkout
+                    if use_existing_checkout
+                    else join(tmp_dir, 'desugar_jdk_libs'))
+    if (not use_existing_checkout):
+      subprocess.check_call(
+          ['git', 'clone', 'https://github.com/google/desugar_jdk_libs.git', checkout_dir])
+      if (args.desugar_jdk_libs_revision):
+        subprocess.check_call(
+            ['git', '-C', checkout_dir, 'checkout', args.desugar_jdk_libs_revision])
+    print("Building desugared library")
+    bazel = os.path.join(utils.BAZEL_TOOL, 'lib', 'bazel', 'bin', 'bazel')
+    with utils.ChangedWorkingDirectory(checkout_dir):
+      for target in [':desugar_jdk_libs_jdk11', '//jdk11/src:java_base_chm_only']:
+        subprocess.check_call([
+            bazel,
+            '--bazelrc=/dev/null',
+            'build',
+            '--spawn_strategy=local',
+            '--verbose_failures',
+            target])
+
+    openjdk_dir = join('third_party', 'openjdk')
+    openjdk_subdir = 'desugar_jdk_libs_11'
+    dest_dir = join(openjdk_dir, openjdk_subdir)
+    src_dir = join(checkout_dir, 'bazel-bin', 'jdk11', 'src')
+
+    metadata_files = ('LICENSE', 'README.google')
+    for f in metadata_files:
+      shutil.copyfile(join(dest_dir, f), join(tmp_dir, f))
+    shutil.rmtree(dest_dir)
+    os.remove(join(openjdk_dir, openjdk_subdir + '.tar.gz'))
+    os.remove(join(openjdk_dir, openjdk_subdir + '.tar.gz.sha1'))
+    os.mkdir(dest_dir)
+    for s in [
+        (join(src_dir, 'd8_java_base_selected_with_addon.jar'),
+          join(dest_dir, 'desugar_jdk_libs.jar')),
+        (join(src_dir, 'java_base_chm_only.jar'),
+          join(dest_dir, 'desugar_jdk_libs_chm_only.jar'))]:
+      shutil.copyfile(s[0], s[1])
+    for f in metadata_files:
+      shutil.copyfile(join(tmp_dir, f), join(dest_dir, f))
+    desugar_jdk_libs_hash = os.path.join(dest_dir, 'desugar_jdk_libs_hash')
+    with open(desugar_jdk_libs_hash, 'w') as desugar_jdk_libs_hash_writer:
+      desugar_jdk_libs_hash_writer.write(GetGitHash(checkout_dir))
+
+  print('Now run')
+  print('  (cd %s; upload_to_google_storage.py -a --bucket r8-deps %s)'
+      % (openjdk_dir, openjdk_subdir))
+
+
+
+def main():
+  args = parse_options()
+  run(args)
+
+def parse_options():
+  parser = argparse.ArgumentParser(
+    description='Script for updating third_party/openjdk/desugar_jdk_libs*')
+  parser.add_argument('--desugar-jdk-libs-checkout', '--desugar_jdk_libs_checkout',
+                      default=None,
+                      metavar=('<path>'),
+                      help='Use existing checkout of github.com/google/desugar_jdk_libs.')
+  parser.add_argument('--desugar-jdk-libs-revision', '--desugar_jdk_libs_revision',
+                      default=None,
+                      metavar=('<revision>'),
+                      help='Revision of github.com/google/desugar_jdk_libs to use.')
+  args = parser.parse_args()
+  return args
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/tools/test.py b/tools/test.py
index 5586802..a4e7b6e 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -255,7 +255,7 @@
     os.makedirs(desugar_jdk_libs_dir)
     print('Building desugared library.')
     with utils.TempDir() as checkout_dir:
-      archive_desugar_jdk_libs.CloneDesugaredLibrary('google', checkout_dir)
+      archive_desugar_jdk_libs.CloneDesugaredLibrary('google', checkout_dir, 'HEAD')
       # Make sure bazel is extracted in third_party.
       utils.DownloadFromGoogleCloudStorage(utils.BAZEL_SHA_FILE)
       utils.DownloadFromGoogleCloudStorage(utils.JAVA8_SHA_FILE)
diff --git a/tools/utils.py b/tools/utils.py
index 1830f53..c5fb4ce 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -109,6 +109,7 @@
 BAZEL_TOOL = os.path.join(THIRD_PARTY, 'bazel')
 JAVA8_SHA_FILE = os.path.join(THIRD_PARTY, 'openjdk', 'jdk8', 'linux-x86.tar.gz.sha1')
 JAVA11_SHA_FILE = os.path.join(THIRD_PARTY, 'openjdk', 'jdk-11', 'linux.tar.gz.sha1')
+DESUGAR_JDK_LIBS_11_SHA_FILE = os.path.join(THIRD_PARTY, 'openjdk', 'desugar_jdk_libs_11.tar.gz.sha1')
 IGNORE_WARNINGS_RULES = os.path.join(REPO_ROOT, 'src', 'test', 'ignorewarnings.rules')
 
 ANDROID_HOME_ENVIROMENT_NAME = "ANDROID_HOME"