Merge commit 'a2e203580aa00a36f85cd68d3d584b97aef34d59' into dev-release
diff --git a/.gitignore b/.gitignore
index ae1be52..405e837 100644
--- a/.gitignore
+++ b/.gitignore
@@ -274,8 +274,8 @@
 tools/*/art-10.0.0.tar.gz
 tools/*/host/art-12.0.0-beta4
 tools/*/host/art-12.0.0-beta4.tar.gz
-tools/*/host/art-13-dev
-tools/*/host/art-13-dev.tar.gz
+tools/*/host/art-13.0.0
+tools/*/host/art-13.0.0.tar.gz
 tools/*/host/art-master
 tools/*/host/art-master.tar.gz
 tools/*/art.tar.gz
diff --git a/build.gradle b/build.gradle
index 8f84e7f..90d75ef 100644
--- a/build.gradle
+++ b/build.gradle
@@ -411,7 +411,7 @@
                 "linux/art-9.0.0",
                 "linux/art-10.0.0",
                 "linux/host/art-12.0.0-beta4",
-                "linux/host/art-13-dev",
+                "linux/host/art-13.0.0",
                 "linux/host/art-master",
                 "linux/dalvik",
                 "linux/dalvik-4.0.4",
diff --git a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
index 2b46e36..d412e52 100644
--- a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
+++ b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
@@ -21,6 +21,7 @@
           "j$/util/stream/Collector$Characteristics");
   private static final Set<String> WRAP_CONVERT_OWNER =
       ImmutableSet.of(
+          "j$/util/stream/Stream",
           "j$/nio/file/spi/FileSystemProvider",
           "j$/nio/file/spi/FileTypeDetector",
           "j$/nio/file/Path",
diff --git a/infra/config/global/generated/cr-buildbucket.cfg b/infra/config/global/generated/cr-buildbucket.cfg
index 0586776..ebe76f2 100644
--- a/infra/config/global/generated/cr-buildbucket.cfg
+++ b/infra/config/global/generated/cr-buildbucket.cfg
@@ -168,7 +168,41 @@
         '  "builder_group": "internal.client.r8",'
         '  "recipe": "rex",'
         '  "test_options": ['
-        '    "--variant=jdk11"'
+        '    "--variant=jdk11_minimal",'
+        '    "--variant=jdk11",'
+        '    "--variant=jdk11_nio"'
+        '  ],'
+        '  "test_wrapper": "tools/archive_desugar_jdk_libs.py"'
+        '}'
+      priority: 25
+      execution_timeout_secs: 3600
+      expiration_secs: 126000
+      build_numbers: YES
+      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+    }
+    builders {
+      name: "lib_desugar-archive-jdk11-legacy"
+      swarming_host: "chrome-swarming.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "pool:luci.r8.ci"
+      exe {
+        cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
+        cipd_version: "refs/heads/master"
+        cmd: "luciexe"
+      }
+      properties:
+        '{'
+        '  "builder_group": "internal.client.r8",'
+        '  "recipe": "rex",'
+        '  "test_options": ['
+        '    "--variant=jdk11_legacy"'
         '  ],'
         '  "test_wrapper": "tools/archive_desugar_jdk_libs.py"'
         '}'
diff --git a/infra/config/global/generated/luci-milo.cfg b/infra/config/global/generated/luci-milo.cfg
index 9aaa005..c550e41 100644
--- a/infra/config/global/generated/luci-milo.cfg
+++ b/infra/config/global/generated/luci-milo.cfg
@@ -126,6 +126,11 @@
     short_name: "jdk11"
   }
   builders {
+    name: "buildbucket/luci.r8.ci/lib_desugar-archive-jdk11-legacy"
+    category: "library_desugar"
+    short_name: "legacy"
+  }
+  builders {
     name: "buildbucket/luci.r8.ci/lib_desugar-archive-jdk8"
     category: "library_desugar"
     short_name: "jdk8"
diff --git a/infra/config/global/generated/luci-scheduler.cfg b/infra/config/global/generated/luci-scheduler.cfg
index 10dd059..b775cde 100644
--- a/infra/config/global/generated/luci-scheduler.cfg
+++ b/infra/config/global/generated/luci-scheduler.cfg
@@ -78,6 +78,21 @@
   }
 }
 job {
+  id: "lib_desugar-archive-jdk11-legacy"
+  realm: "ci"
+  acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 3
+    max_batch_size: 1
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "ci"
+    builder: "lib_desugar-archive-jdk11-legacy"
+  }
+}
+job {
   id: "lib_desugar-archive-jdk8"
   realm: "ci"
   acl_sets: "ci"
diff --git a/infra/config/global/generated/project.cfg b/infra/config/global/generated/project.cfg
index 53f90a7..bb18107 100644
--- a/infra/config/global/generated/project.cfg
+++ b/infra/config/global/generated/project.cfg
@@ -7,7 +7,7 @@
 name: "r8"
 access: "group:all"
 lucicfg {
-  version: "1.30.11"
+  version: "1.32.1"
   package_dir: ".."
   config_dir: "generated"
   entry_point: "main.star"
diff --git a/infra/config/global/main.star b/infra/config/global/main.star
index f93a678..73e0802 100755
--- a/infra/config/global/main.star
+++ b/infra/config/global/main.star
@@ -240,14 +240,24 @@
             dimensions = dimensions, category = category, release_trigger=release_trigger)
 
 def archivers():
-  for name in ["archive", "archive_release", "lib_desugar-archive-jdk11", "lib_desugar-archive-jdk8"]:
+  for name in [
+      "archive",
+      "archive_release",
+      "lib_desugar-archive-jdk11",
+      "lib_desugar-archive-jdk11-legacy",
+      "lib_desugar-archive-jdk8"]:
     desugar = "desugar" in name
     properties = {
         "test_wrapper" : "tools/archive_desugar_jdk_libs.py" if desugar else "tools/archive.py",
         "builder_group" : "internal.client.r8"
     }
     if desugar:
-      properties["test_options"] = ["--variant=jdk11" if "jdk11" in name else "--variant=jdk8"]
+      if name.endswith("jdk11"):
+        properties["test_options"] = ["--variant=jdk11_minimal", "--variant=jdk11", "--variant=jdk11_nio"]
+      elif name.endswith("jdk11-legacy"):
+        properties["test_options"] = ["--variant=jdk11_legacy"]
+      else:
+        properties["test_options"] = ["--variant=jdk8"]
 
     r8_builder(
         name,
diff --git a/src/library_desugar/desugar_jdk_libs.json b/src/library_desugar/desugar_jdk_libs.json
index 1b2e6bf..6f57d4c 100644
--- a/src/library_desugar/desugar_jdk_libs.json
+++ b/src/library_desugar/desugar_jdk_libs.json
@@ -2,7 +2,7 @@
   "configuration_format_version": 3,
   "group_id" : "com.tools.android",
   "artifact_id" : "desugar_jdk_libs",
-  "version": "1.1.7",
+  "version": "1.1.8",
   "required_compilation_api_level": 26,
   "synthesized_library_classes_package_prefix": "j$.",
   "support_all_callbacks_from_library": true,
@@ -10,7 +10,8 @@
     {
       "api_level_below_or_equal": 25,
       "wrapper_conversion": [
-        "java.time.Clock"
+        "java.time.Clock",
+        "java.time.temporal.ChronoUnit"
       ]
     },
     {
diff --git a/src/library_desugar/java/j$/util/stream/Stream.java b/src/library_desugar/java/j$/util/stream/Stream.java
new file mode 100644
index 0000000..70c761a
--- /dev/null
+++ b/src/library_desugar/java/j$/util/stream/Stream.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 Stream<T> {
+
+  public static java.util.stream.Stream<?> inverted_wrap_convert(j$.util.stream.Stream<?> stream) {
+    return null;
+  }
+
+  public static j$.util.stream.Stream<?> inverted_wrap_convert(java.util.stream.Stream<?> stream) {
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/java/util/stream/StackWalkerApiFlips.java b/src/library_desugar/java/java/util/stream/StackWalkerApiFlips.java
new file mode 100644
index 0000000..300c5ad
--- /dev/null
+++ b/src/library_desugar/java/java/util/stream/StackWalkerApiFlips.java
@@ -0,0 +1,44 @@
+// 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.Function;
+
+public class StackWalkerApiFlips {
+
+  public static Function<?, ?> flipFunctionStream(Function<?, ?> stackWalker) {
+    return new FunctionStreamWrapper<>(stackWalker);
+  }
+
+  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 T flipStream(T maybeStream) {
+      if (maybeStream == null) {
+        return null;
+      }
+      if (maybeStream instanceof java.util.stream.Stream<?>) {
+        return (T)
+            j$.util.stream.Stream.inverted_wrap_convert((java.util.stream.Stream<?>) maybeStream);
+      }
+      if (maybeStream instanceof j$.util.stream.Stream<?>) {
+        return (T)
+            j$.util.stream.Stream.inverted_wrap_convert((j$.util.stream.Stream<?>) maybeStream);
+      }
+      throw exception("java.util.stream.Stream", maybeStream.getClass());
+    }
+
+    public R apply(T arg) {
+      return function.apply(flipStream(arg));
+    }
+  }
+}
diff --git a/src/library_desugar/jdk11/chm_only_desugar_jdk_libs.json b/src/library_desugar/jdk11/chm_only_desugar_jdk_libs.json
index b1ebcd8..6c0dd3d 100644
--- a/src/library_desugar/jdk11/chm_only_desugar_jdk_libs.json
+++ b/src/library_desugar/jdk11/chm_only_desugar_jdk_libs.json
@@ -20,6 +20,11 @@
       "rewrite_prefix": {
         "java.util.concurrent.Helpers": "j$.util.concurrent.Helpers",
         "sun.misc.Desugar": "j$.sun.misc.Desugar"
+      },
+      "rewrite_derived_prefix": {
+        "sun.misc.DesugarUnsafe": {
+          "jdk.internal.misc.Unsafe": "j$.sun.misc.DesugarUnsafe"
+        }
       }
     }
   ],
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs.json b/src/library_desugar/jdk11/desugar_jdk_libs.json
index e3caa6e..103035f 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs.json
@@ -1,5 +1,5 @@
 {
-  "identifier": "com.tools.android:desugar_jdk_libs:2.0.0",
+  "identifier": "com.tools.android:desugar_jdk_libs_configuration:2.0.0",
   "configuration_format_version": 100,
   "required_compilation_api_level": 30,
   "synthesized_library_classes_package_prefix": "j$.",
@@ -20,7 +20,8 @@
         "java.time.ZonedDateTime java.util.GregorianCalendar#toZonedDateTime()": "java.util.DesugarGregorianCalendar"
       },
       "wrapper_conversion": [
-        "java.time.Clock"
+        "java.time.Clock",
+        "java.time.temporal.ChronoUnit"
       ],
       "custom_conversion": {
         "java.time.Duration": "java.time.TimeConversions",
@@ -98,8 +99,12 @@
         "java.util.Spliterator java.util.LinkedHashSet#spliterator()": "java.util.DesugarLinkedHashSet"
       },
       "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.Set java.util.stream.Collector#characteristics()" : [-1, "java.util.Set java.util.stream.StreamApiFlips#flipCharacteristicSet(java.util.Set)"],
+        "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": [
+        "java.lang.Object java.lang.StackWalker#walk(java.util.function.Function)"
+      ],
       "wrapper_conversion": [
         "java.util.PrimitiveIterator$OfDouble",
         "java.util.PrimitiveIterator$OfInt",
@@ -179,6 +184,9 @@
       "rewrite_derived_prefix": {
         "java.time.": {
           "j$.time.": "java.time."
+        },
+        "sun.misc.DesugarUnsafe": {
+          "jdk.internal.misc.Unsafe": "j$.sun.misc.DesugarUnsafe"
         }
       },
       "backport": {
@@ -225,6 +233,9 @@
         },
         "java.util.Optional": {
           "j$.util.Optional": "java.util.Optional"
+        },
+        "java.util.stream.Stream": {
+          "j$.util.stream.Stream": "java.util.stream.Stream"
         }
       }
     }
@@ -246,4 +257,4 @@
     "-dontwarn sun.misc.Unsafe",
     "-dontwarn wrapper.**"
   ]
-}
\ No newline at end of file
+}
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_legacy.json b/src/library_desugar/jdk11/desugar_jdk_libs_legacy.json
index a4ddd2ba..7d17a1d 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_legacy.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_legacy.json
@@ -2,7 +2,7 @@
   "configuration_format_version": 5,
   "group_id" : "com.tools.android",
   "artifact_id" : "desugar_jdk_libs",
-  "version": "1.2.1",
+  "version": "1.2.2",
   "required_compilation_api_level": 30,
   "synthesized_library_classes_package_prefix": "j$.",
   "support_all_callbacks_from_library": true,
@@ -19,7 +19,8 @@
         "java.util.GregorianCalendar#toZonedDateTime": "java.util.DesugarGregorianCalendar"
       },
       "wrapper_conversion": [
-        "java.time.Clock"
+        "java.time.Clock",
+        "java.time.temporal.ChronoUnit"
       ],
       "custom_conversion": {
         "java.time.Duration": "java.time.TimeConversions",
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json b/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json
index 8064ed0..d13b947 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json
@@ -1,5 +1,5 @@
 {
-  "identifier": "com.tools.android:desugar_jdk_libs_minimal:2.0.0",
+  "identifier": "com.tools.android:desugar_jdk_libs_configuration_minimal:2.0.0",
   "configuration_format_version": 100,
   "required_compilation_api_level": 24,
   "synthesized_library_classes_package_prefix": "j$.",
@@ -22,4 +22,4 @@
     "-keepattributes EnclosingMethod",
     "-keepattributes InnerClasses"
   ]
-}
\ No newline at end of file
+}
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_path.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
similarity index 95%
rename from src/library_desugar/jdk11/desugar_jdk_libs_path.json
rename to src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index 4128a53..a53fd3b 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_path.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -1,5 +1,5 @@
 {
-  "identifier": "com.tools.android:desugar_jdk_libs:2.0.0",
+  "identifier": "com.tools.android:desugar_jdk_libs_configuration_nio:2.0.0",
   "configuration_format_version": 100,
   "required_compilation_api_level": 30,
   "synthesized_library_classes_package_prefix": "j$.",
@@ -34,7 +34,8 @@
         "java.time.ZonedDateTime java.util.GregorianCalendar#toZonedDateTime()": "java.util.DesugarGregorianCalendar"
       },
       "wrapper_conversion": [
-        "java.time.Clock"
+        "java.time.Clock",
+        "java.time.temporal.ChronoUnit"
       ],
       "custom_conversion": {
         "java.time.Duration": "java.time.TimeConversions",
@@ -235,8 +236,12 @@
         "java.util.Spliterator java.util.LinkedHashSet#spliterator()": "java.util.DesugarLinkedHashSet"
       },
       "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.Set java.util.stream.Collector#characteristics()" : [-1, "java.util.Set java.util.stream.StreamApiFlips#flipCharacteristicSet(java.util.Set)"],
+        "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": [
+        "java.lang.Object java.lang.StackWalker#walk(java.util.function.Function)"
+      ],
       "wrapper_conversion": [
         "java.nio.channels.SeekableByteChannel",
         "java.util.PrimitiveIterator$OfDouble",
@@ -280,7 +285,6 @@
     {
       "api_level_below_or_equal": 18,
       "rewrite_prefix": {
-        "java.lang.DesugarCharacter": "j$.lang.DesugarCharacter",
         "java.nio.charset.StandardCharsets": "j$.nio.charset.StandardCharsets"
       },
       "retarget_method": {
@@ -332,6 +336,12 @@
       "dont_retarget": [
         "android.support.multidex.MultiDexExtractor$ExtractedDex"
       ]
+    },
+    {
+      "api_level_below_or_equal": 18,
+      "rewrite_prefix": {
+        "java.lang.DesugarCharacter": "j$.lang.DesugarCharacter"
+      }
     }
   ],
   "library_flags": [
@@ -341,7 +351,6 @@
         "desugar.": "j$.desugar.",
         "libcore.": "j$.libcore.",
         "java.lang.Desugar": "j$.lang.Desugar",
-        "java.lang.ref.Cleaner": "j$.lang.ref.Cleaner",
         "sun.security.action.": "j$.sun.security.action."
       }
     },
@@ -369,6 +378,15 @@
         "java.util.ConversionRuntimeException": "j$.util.ConversionRuntimeException"
       },
       "rewrite_derived_prefix": {
+        "desugar.sun.nio.fs.DesugarDefaultFileSystemProvider": {
+          "sun.nio.fs.DefaultFileSystemProvider": "j$.adapter.HybridFileSystemProvider"
+        },
+        "desugar.sun.nio.fs.DesugarDefaultFileTypeDetector": {
+          "sun.nio.fs.DefaultFileTypeDetector": "j$.adapter.HybridFileTypeDetector"
+        },
+        "sun.misc.DesugarUnsafe": {
+          "jdk.internal.misc.Unsafe" : "j$.sun.misc.DesugarUnsafe"
+        },
         "java.nio.file.attribute.": {
           "j$.nio.file.attribute.": "java.nio.file.attribute."
         },
@@ -435,6 +453,9 @@
         },
         "java.util.Optional": {
           "j$.util.Optional": "java.util.Optional"
+        },
+        "java.util.stream.Stream": {
+          "j$.util.stream.Stream": "java.util.stream.Stream"
         }
       }
     },
@@ -464,4 +485,4 @@
     "-dontwarn sun.misc.Unsafe",
     "-dontwarn wrapper.**"
   ]
-}
\ No newline at end of file
+}
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_path_alternative_3.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio_alternative_3.json
similarity index 100%
rename from src/library_desugar/jdk11/desugar_jdk_libs_path_alternative_3.json
rename to src/library_desugar/jdk11/desugar_jdk_libs_nio_alternative_3.json
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 08c7a84..e8e5a04 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -12,6 +12,7 @@
 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.startup.StartupProfileProvider;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DumpInputFlags;
@@ -21,6 +22,7 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -55,6 +57,7 @@
   private final MapIdProvider mapIdProvider;
   private final SourceFileProvider sourceFileProvider;
   private final boolean isAndroidPlatformBuild;
+  private final List<StartupProfileProvider> startupProfileProviders;
 
   BaseCompilerCommand(boolean printHelp, boolean printVersion) {
     super(printHelp, printVersion);
@@ -74,6 +77,7 @@
     mapIdProvider = null;
     sourceFileProvider = null;
     isAndroidPlatformBuild = false;
+    startupProfileProviders = null;
   }
 
   BaseCompilerCommand(
@@ -93,7 +97,8 @@
       DumpInputFlags dumpInputFlags,
       MapIdProvider mapIdProvider,
       SourceFileProvider sourceFileProvider,
-      boolean isAndroidPlatformBuild) {
+      boolean isAndroidPlatformBuild,
+      List<StartupProfileProvider> startupProfileProviders) {
     super(app);
     assert minApiLevel > 0;
     assert mode != null;
@@ -113,6 +118,7 @@
     this.mapIdProvider = mapIdProvider;
     this.sourceFileProvider = sourceFileProvider;
     this.isAndroidPlatformBuild = isAndroidPlatformBuild;
+    this.startupProfileProviders = startupProfileProviders;
   }
 
   /**
@@ -208,6 +214,10 @@
     return isAndroidPlatformBuild;
   }
 
+  List<StartupProfileProvider> getStartupProfileProviders() {
+    return startupProfileProviders;
+  }
+
   DumpInputFlags getDumpInputFlags() {
     return dumpInputFlags;
   }
@@ -249,6 +259,7 @@
     private MapIdProvider mapIdProvider = null;
     private SourceFileProvider sourceFileProvider = null;
     private boolean isAndroidPlatformBuild = false;
+    private List<StartupProfileProvider> startupProfileProviders = new ArrayList<>();
 
     abstract CompilationMode defaultCompilationMode();
 
@@ -664,6 +675,19 @@
       return isAndroidPlatformBuild;
     }
 
+    B addStartupProfileProviders(StartupProfileProvider... startupProfileProviders) {
+      return addStartupProfileProviders(Arrays.asList(startupProfileProviders));
+    }
+
+    B addStartupProfileProviders(Collection<StartupProfileProvider> startupProfileProviders) {
+      this.startupProfileProviders.addAll(startupProfileProviders);
+      return self();
+    }
+
+    List<StartupProfileProvider> getStartupProfileProviders() {
+      return startupProfileProviders;
+    }
+
     /**
      * Allow to skip to dump into file and dump into directory instruction, this is primarily used
      * for chained compilation in L8 so there are no duplicated dumps.
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 76c2966..172bfa2 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.shaking.ProguardConfigurationSource;
 import com.android.tools.r8.shaking.ProguardConfigurationSourceFile;
 import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings;
+import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AssertionConfigurationWithDefault;
@@ -264,6 +265,35 @@
       return self();
     }
 
+    /**
+     * Add a collection of startup profile providers that should be used for distributing the
+     * program classes in dex.
+     *
+     * <p>NOTE: Startup profiles are ignored when compiling to class files or the min-API level does
+     * not support native multidex (API<=20).
+     *
+     * <p>NOTE: This API is experimental and may be subject to changes.
+     */
+    @Override
+    public Builder addStartupProfileProviders(StartupProfileProvider... startupProfileProviders) {
+      return super.addStartupProfileProviders(startupProfileProviders);
+    }
+
+    /**
+     * Add a collection of startup profile providers that should be used for distributing the
+     * program classes in dex.
+     *
+     * <p>NOTE: Startup profiles are ignored when compiling to class files or the min-API level does
+     * not support native multidex (API<=20).
+     *
+     * <p>NOTE: This API is experimental and may be subject to changes.
+     */
+    @Override
+    public Builder addStartupProfileProviders(
+        Collection<StartupProfileProvider> startupProfileProviders) {
+      return super.addStartupProfileProviders(startupProfileProviders);
+    }
+
     @Override
     Builder self() {
       return this;
@@ -426,6 +456,7 @@
           proguardMapConsumer,
           enableMissingLibraryApiModeling,
           getAndroidPlatformBuild(),
+          getStartupProfileProviders(),
           factory);
     }
   }
@@ -516,6 +547,7 @@
       StringConsumer proguardMapConsumer,
       boolean enableMissingLibraryApiModeling,
       boolean isAndroidPlatformBuild,
+      List<StartupProfileProvider> startupProfileProviders,
       DexItemFactory factory) {
     super(
         inputApp,
@@ -534,7 +566,8 @@
         dumpInputFlags,
         mapIdProvider,
         null,
-        isAndroidPlatformBuild);
+        isAndroidPlatformBuild,
+        startupProfileProviders);
     this.intermediate = intermediate;
     this.globalSyntheticsConsumer = globalSyntheticsConsumer;
     this.desugarGraphConsumer = desugarGraphConsumer;
@@ -615,7 +648,10 @@
     internal.dexClassChecksumFilter = getDexClassChecksumFilter();
     internal.enableInheritanceClassInDexDistributor = isOptimizeMultidexForLinearAlloc();
     internal.setDesugaredLibrarySpecification(desugaredLibrarySpecification);
-    internal.synthesizedClassPrefix = synthesizedClassPrefix;
+    internal.synthesizedClassPrefix =
+        synthesizedClassPrefix.isEmpty()
+            ? System.getProperty("com.android.tools.r8.synthesizedClassPrefix", "")
+            : synthesizedClassPrefix;
     internal.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
 
     if (!enableMissingLibraryApiModeling) {
@@ -654,6 +690,11 @@
 
     internal.configureAndroidPlatformBuild(getAndroidPlatformBuild());
 
+    // TODO(b/238173796): Change StartupOptions to store a Collection<StartupProfileProvider>.
+    if (getStartupProfileProviders().size() == 1) {
+      internal.getStartupOptions().setStartupProfileProvider(getStartupProfileProviders().get(0));
+    }
+
     internal.setDumpInputFlags(getDumpInputFlags());
     internal.dumpOptions = dumpOptions();
 
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index b9fe11b..4069ece 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -117,7 +117,8 @@
         dumpInputFlags,
         mapIdProvider,
         null,
-        false);
+        false,
+        null);
     this.d8Command = d8Command;
     this.r8Command = r8Command;
     this.desugaredLibrarySpecification = desugaredLibrarySpecification;
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 9818440..6adecce 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.shaking.ProguardConfigurationSourceBytes;
 import com.android.tools.r8.shaking.ProguardConfigurationSourceFile;
 import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings;
+import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AssertionConfigurationWithDefault;
@@ -447,6 +448,37 @@
       return super.createProgramOutputConsumer(path, mode, consumeDataResources);
     }
 
+    /**
+     * Add a collection of startup profile providers that should be used for distributing the
+     * program classes in dex. The given startup profiles are also used to disallow optimizations
+     * across the startup and post-startup boundary.
+     *
+     * <p>NOTE: Startup profiles are ignored when compiling to class files or the min-API level does
+     * not support native multidex (API<=20).
+     *
+     * <p>NOTE: This API is experimental and may be subject to changes.
+     */
+    @Override
+    public Builder addStartupProfileProviders(StartupProfileProvider... startupProfileProviders) {
+      return super.addStartupProfileProviders(startupProfileProviders);
+    }
+
+    /**
+     * Add a collection of startup profile providers that should be used for distributing the
+     * program classes in dex. The given startup profiles are also used to disallow optimizations
+     * across the startup and post-startup boundary.
+     *
+     * <p>NOTE: Startup profiles are ignored when compiling to class files or the min-API level does
+     * not support native multidex (API<=20).
+     *
+     * <p>NOTE: This API is experimental and may be subject to changes.
+     */
+    @Override
+    public Builder addStartupProfileProviders(
+        Collection<StartupProfileProvider> startupProfileProviders) {
+      return super.addStartupProfileProviders(startupProfileProviders);
+    }
+
     @Override
     void validate() {
       if (isPrintHelp()) {
@@ -628,7 +660,8 @@
               getMapIdProvider(),
               getSourceFileProvider(),
               enableMissingLibraryApiModeling,
-              getAndroidPlatformBuild());
+              getAndroidPlatformBuild(),
+              getStartupProfileProviders());
 
       if (inputDependencyGraphConsumer != null) {
         inputDependencyGraphConsumer.finished();
@@ -814,7 +847,8 @@
       MapIdProvider mapIdProvider,
       SourceFileProvider sourceFileProvider,
       boolean enableMissingLibraryApiModeling,
-      boolean isAndroidPlatformBuild) {
+      boolean isAndroidPlatformBuild,
+      List<StartupProfileProvider> startupProfileProviders) {
     super(
         inputApp,
         mode,
@@ -832,7 +866,8 @@
         dumpInputFlags,
         mapIdProvider,
         sourceFileProvider,
-        isAndroidPlatformBuild);
+        isAndroidPlatformBuild,
+        startupProfileProviders);
     assert proguardConfiguration != null;
     assert mainDexKeepRules != null;
     this.mainDexKeepRules = mainDexKeepRules;
@@ -1011,9 +1046,12 @@
     internal.enableInheritanceClassInDexDistributor = isOptimizeMultidexForLinearAlloc();
 
     internal.setDesugaredLibrarySpecification(desugaredLibrarySpecification);
-    internal.synthesizedClassPrefix = synthesizedClassPrefix;
-    // TODO(b/214382176): Enable all the time.
+    internal.synthesizedClassPrefix =
+        synthesizedClassPrefix.isEmpty()
+            ? System.getProperty("com.android.tools.r8.synthesizedClassPrefix", "")
+            : synthesizedClassPrefix;
     boolean l8Shrinking = !synthesizedClassPrefix.isEmpty();
+    // TODO(b/214382176): Enable all the time.
     internal.loadAllClassDefinitions = l8Shrinking;
     if (l8Shrinking) {
       internal.apiModelingOptions().disableSubbingOfClasses();
@@ -1029,6 +1067,11 @@
 
     internal.configureAndroidPlatformBuild(getAndroidPlatformBuild());
 
+    // TODO(b/238173796): Change StartupOptions to store a Collection<StartupProfileProvider>.
+    if (getStartupProfileProviders().size() == 1) {
+      internal.getStartupOptions().setStartupProfileProvider(getStartupProfileProviders().get(0));
+    }
+
     if (!DETERMINISTIC_DEBUGGING) {
       assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
       internal.threadCount = getThreadCount();
diff --git a/src/main/java/com/android/tools/r8/StartupProfileProvider.java b/src/main/java/com/android/tools/r8/StartupProfileProvider.java
deleted file mode 100644
index fc5e4b1..0000000
--- a/src/main/java/com/android/tools/r8/StartupProfileProvider.java
+++ /dev/null
@@ -1,13 +0,0 @@
-// 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;
-
-/** Interface for providing a startup profile to the compiler. */
-@FunctionalInterface
-public interface StartupProfileProvider {
-
-  /** Return the startup profile. */
-  String get();
-}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index a64b883..7db4fbd 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -276,7 +276,8 @@
                         content,
                         options.reporter,
                         false,
-                        options.testing.enableExperimentalMapFileVersion));
+                        options.testing.enableExperimentalMapFileVersion,
+                        false));
               } catch (IOException | ResourceException e) {
                 throw new CompilationError("Failure to read proguard map file", e, map.getOrigin());
               }
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 7d1b4dc..86df003 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
@@ -6,8 +6,11 @@
 
 import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault;
 
-import com.android.tools.r8.StartupProfileProvider;
 import com.android.tools.r8.StringResource;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.startup.StartupProfileBuilder;
+import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.utils.SystemPropertyUtils;
 import java.nio.file.Paths;
 
@@ -53,7 +56,23 @@
       SystemPropertyUtils.applySystemProperty(
           "com.android.tools.r8.startup.profile",
           propertyValue ->
-              StringResource.fromFile(Paths.get(propertyValue))::getStringWithRuntimeException,
+              new StartupProfileProvider() {
+                @Override
+                public String get() {
+                  return StringResource.fromFile(Paths.get(propertyValue))
+                      .getStringWithRuntimeException();
+                }
+
+                @Override
+                public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) {
+                  throw new Unimplemented();
+                }
+
+                @Override
+                public Origin getOrigin() {
+                  return Origin.unknown();
+                }
+              },
           () -> null);
 
   public boolean isMinimalStartupDexEnabled() {
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupProfile.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfile.java
index 20cd31b..6b6076f 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupProfile.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfile.java
@@ -4,9 +4,9 @@
 
 package com.android.tools.r8.experimental.startup;
 
-import com.android.tools.r8.StartupProfileProvider;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.StringUtils;
diff --git a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
index 68af78a..fd710bb 100644
--- a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
+++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
@@ -327,11 +326,13 @@
 
   // Static helpers to avoid verbose predicates.
 
-  private static ClassToFeatureSplitMap getMap(AppView<AppInfoWithLiveness> appView) {
+  private static ClassToFeatureSplitMap getMap(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
     return appView.appInfo().getClassToFeatureSplitMap();
   }
 
-  public static boolean isInFeature(DexProgramClass clazz, AppView<AppInfoWithLiveness> appView) {
+  public static boolean isInFeature(
+      DexProgramClass clazz, AppView<? extends AppInfoWithClassHierarchy> appView) {
     return getMap(appView)
         .isInFeature(
             clazz,
diff --git a/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java b/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
new file mode 100644
index 0000000..451ff92
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
@@ -0,0 +1,75 @@
+// 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.features;
+
+import com.android.tools.r8.FeatureSplit;
+import com.android.tools.r8.experimental.startup.StartupOrder;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMember;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.InternalOptions;
+
+public class FeatureSplitBoundaryOptimizationUtils {
+
+  public static ConstraintWithTarget getInliningConstraintForResolvedMember(
+      ProgramMethod method,
+      DexEncodedMember<?, ?> resolvedMember,
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
+    // We never inline into the base from a feature (calls should never happen) and we never inline
+    // between features, so this check should be sufficient.
+    if (classToFeatureSplitMap.isInBaseOrSameFeatureAs(
+        resolvedMember.getHolderType(), method, appView)) {
+      return ConstraintWithTarget.ALWAYS;
+    }
+    return ConstraintWithTarget.NEVER;
+  }
+
+  public static FeatureSplit getMergeKeyForHorizontalClassMerging(
+      DexProgramClass clazz, AppView<? extends AppInfoWithClassHierarchy> appView) {
+    ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
+    return classToFeatureSplitMap.getFeatureSplit(clazz, appView);
+  }
+
+  public static boolean isSafeForAccess(
+      DexProgramClass accessedClass,
+      ProgramDefinition accessor,
+      ClassToFeatureSplitMap classToFeatureSplitMap,
+      InternalOptions options,
+      StartupOrder startupOrder,
+      SyntheticItems syntheticItems) {
+    return classToFeatureSplitMap.isInBaseOrSameFeatureAs(
+        accessedClass, accessor, options, startupOrder, syntheticItems);
+  }
+
+  public static boolean isSafeForInlining(
+      ProgramMethod caller,
+      ProgramMethod callee,
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
+    if (classToFeatureSplitMap.isInSameFeatureOrBothInSameBase(callee, caller, appView)) {
+      return true;
+    }
+    // Still allow inlining if we inline from the base into a feature.
+    if (classToFeatureSplitMap.isInBase(callee.getHolder(), appView)) {
+      return true;
+    }
+    return false;
+  }
+
+  public static boolean isSafeForVerticalClassMerging(
+      DexProgramClass sourceClass,
+      DexProgramClass targetClass,
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
+    return classToFeatureSplitMap.isInSameFeatureOrBothInSameBase(
+        sourceClass, targetClass, appView);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
index 43bbfa2..9211db6 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.experimental.startup.StartupOrder;
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
+import com.android.tools.r8.features.FeatureSplitBoundaryOptimizationUtils;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OptionalBool;
@@ -42,9 +43,10 @@
     }
     if (clazz.isProgramClass()
         && context.isProgramDefinition()
-        && !classToFeatureSplitMap.isInBaseOrSameFeatureAs(
+        && !FeatureSplitBoundaryOptimizationUtils.isSafeForAccess(
             clazz.asProgramClass(),
             context.asProgramDefinition(),
+            classToFeatureSplitMap,
             options,
             startupOrder,
             syntheticItems)) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
index 73dfa2a..5ad8747 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.horizontalclassmerging.policies;
 
 import com.android.tools.r8.FeatureSplit;
+import com.android.tools.r8.features.FeatureSplitBoundaryOptimizationUtils;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -19,7 +20,8 @@
 
   @Override
   public FeatureSplit getMergeKey(DexProgramClass clazz) {
-    return appView.appInfo().getClassToFeatureSplitMap().getFeatureSplit(clazz, appView);
+    return FeatureSplitBoundaryOptimizationUtils.getMergeKeyForHorizontalClassMerging(
+        clazz, appView);
   }
 
   @Override
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 edd3468..76e9d15 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
@@ -16,6 +16,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 java.util.Set;
 
 /**
@@ -174,4 +175,9 @@
       return this;
     }
   }
+
+  @Override
+  public void buildLIR(LIRBuilder<Value> builder) {
+    builder.addArgument(index, knownToBeBoolean);
+  }
 }
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 ca71070..afb3203 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
@@ -22,6 +22,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
+import com.android.tools.r8.lightir.LIRBuilder;
 import java.io.UTFDataFormatException;
 
 public class ConstString extends ConstInstruction {
@@ -179,4 +180,9 @@
     assert getOutType().equals(expectedType);
     return true;
   }
+
+  @Override
+  public void buildLIR(LIRBuilder<Value> 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 e5bdb6d..1f87da5 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
@@ -13,6 +13,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;
 
 public class DebugPosition extends Instruction {
 
@@ -98,4 +99,9 @@
   public boolean isAllowedAfterThrowingInstruction() {
     return true;
   }
+
+  @Override
+  public void buildLIR(LIRBuilder<Value> builder) {
+    builder.addDebugPosition(getPosition());
+  }
 }
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 7cb6f50..55d96c5 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
@@ -33,6 +33,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;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.InternalOptions;
@@ -1556,6 +1557,10 @@
     return false;
   }
 
+  public void buildLIR(LIRBuilder<Value> builder) {
+    throw new Unimplemented("Missing impl for " + getClass().getSimpleName());
+  }
+
   public static class SideEffectAssumption {
 
     public static final SideEffectAssumption NONE = new SideEffectAssumption();
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 ff70fed..25d775a 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
@@ -28,6 +28,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.shaking.AppInfoWithLiveness;
 import java.util.List;
 
@@ -215,6 +216,11 @@
     return LibraryMethodReadSetModeling.getModeledReadSetOrUnknown(appView, this);
   }
 
+  @Override
+  public void buildLIR(LIRBuilder<Value> builder) {
+    builder.addInvokeDirect(getInvokedMethod(), arguments());
+  }
+
   public static class Builder extends InvokeMethod.Builder<Builder, InvokeDirect> {
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index fed5f58..f44d128 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
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 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.shaking.AppInfoWithLiveness;
 import java.util.List;
 
@@ -175,4 +176,9 @@
       return this;
     }
   }
+
+  @Override
+  public void buildLIR(LIRBuilder<Value> builder) {
+    builder.addInvokeVirtual(getInvokedMethod(), arguments());
+  }
 }
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 752b30c..935e9df 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
@@ -19,6 +19,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LIRBuilder;
 
 public class Return extends JumpInstruction {
 
@@ -150,4 +151,13 @@
       return this;
     }
   }
+
+  @Override
+  public void buildLIR(LIRBuilder<Value> builder) {
+    if (hasReturnValue()) {
+      builder.addReturn(returnValue());
+    } else {
+      builder.addReturnVoid();
+    }
+  }
 }
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 cdec1d9..19b9b27 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
@@ -30,6 +30,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 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.shaking.AppInfoWithLiveness;
 import java.util.Set;
 
@@ -290,4 +291,9 @@
       return this;
     }
   }
+
+  @Override
+  public void buildLIR(LIRBuilder<Value> builder) {
+    builder.addStaticGet(getField());
+  }
 }
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 33f3f28..c62d786 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
@@ -46,6 +46,7 @@
 import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceApplicationRewriter;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
 import com.android.tools.r8.ir.desugar.itf.InterfaceProcessor;
+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.AssertionsRewriter;
@@ -81,6 +82,9 @@
 import com.android.tools.r8.ir.optimize.outliner.Outliner;
 import com.android.tools.r8.ir.optimize.string.StringBuilderAppendOptimizer;
 import com.android.tools.r8.ir.optimize.string.StringOptimizer;
+import com.android.tools.r8.lightir.IR2LIRConverter;
+import com.android.tools.r8.lightir.LIR2IRConverter;
+import com.android.tools.r8.lightir.LIRCode;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.IdentifierNameStringMarker;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
@@ -365,6 +369,7 @@
 
     if (appView.options().isDesugaredLibraryCompilation()) {
       new EmulatedInterfaceApplicationRewriter(appView).rewriteApplication(builder);
+      new L8InnerOuterAttributeEraser(appView).run();
     }
     processCovariantReturnTypeAnnotations(builder);
 
@@ -1611,6 +1616,9 @@
       OptimizationFeedback feedback,
       BytecodeMetadataProvider bytecodeMetadataProvider,
       Timing timing) {
+    if (options.testing.roundtripThroughLIR) {
+      code = roundtripThroughLIR(code, feedback, bytecodeMetadataProvider, timing);
+    }
     if (options.isGeneratingClassFiles()) {
       finalizeToCf(code, feedback, bytecodeMetadataProvider, timing);
     } else {
@@ -1619,6 +1627,16 @@
     }
   }
 
+  private IRCode roundtripThroughLIR(
+      IRCode code,
+      OptimizationFeedback feedback,
+      BytecodeMetadataProvider bytecodeMetadataProvider,
+      Timing timing) {
+    LIRCode lirCode = IR2LIRConverter.translate(code);
+    IRCode irCode = LIR2IRConverter.translate(code.context(), lirCode, appView);
+    return irCode;
+  }
+
   private void finalizeToCf(
       IRCode code,
       OptimizationFeedback feedback,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index fcf37fb..6a9fb76 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -201,6 +201,11 @@
     }
 
     @Override
+    public void acceptGenericApiConversionStub(DexClasspathClass dexClasspathClass) {
+      // Intentionally empty.
+    }
+
+    @Override
     public void acceptAPIConversion(ProgramMethod method) {
       methodProcessor.scheduleDesugaredMethodForProcessing(method);
     }
@@ -378,6 +383,11 @@
     }
 
     @Override
+    public void acceptGenericApiConversionStub(DexClasspathClass clazz) {
+      additions.addLiveClasspathClass(clazz);
+    }
+
+    @Override
     public void acceptAPIConversion(ProgramMethod method) {
       // Intentionally empty. The method will be hit by tracing if required.
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
index e67d55a..f96680f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
@@ -123,6 +123,11 @@
     public void acceptEnumConversionClasspathClass(DexClasspathClass clazz) {
       // Intentionally empty.
     }
+
+    @Override
+    public void acceptGenericApiConversionStub(DexClasspathClass dexClasspathClass) {
+      // Intentionally empty.
+    }
   }
 
   public static class R8PostProcessingDesugaringEventConsumer
@@ -199,5 +204,10 @@
     public void acceptEnumConversionClasspathClass(DexClasspathClass clazz) {
       additions.addLiveClasspathClass(clazz);
     }
+
+    @Override
+    public void acceptGenericApiConversionStub(DexClasspathClass clazz) {
+      additions.addLiveClasspathClass(clazz);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibrarySpecification.java
index 233374b..de42de1 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibrarySpecification.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
 import java.util.List;
+import java.util.Set;
 
 public interface DesugaredLibrarySpecification {
 
@@ -31,6 +32,8 @@
 
   List<String> getExtraKeepRules();
 
+  Set<String> getMaintainTypeOrPrefixForTesting();
+
   AndroidApiLevel getRequiredCompilationApiLevel();
 
   MachineDesugaredLibrarySpecification toMachineSpecification(DexApplication app, Timing timing)
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java
index b4ece95..eeebd32 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java
@@ -148,6 +148,14 @@
     if (isAlreadyDesugared(invoke, context)) {
       return false;
     }
+    if (appView
+            .options()
+            .machineDesugaredLibrarySpecification
+            .getApiGenericConversion()
+            .get(invokedMethod.getReference())
+        != null) {
+      return true;
+    }
     return appView.typeRewriter.hasRewrittenTypeInSignature(invokedMethod.getProto(), appView);
   }
 
@@ -262,6 +270,15 @@
     }
     DexClassAndMethod methodForDesugaring = getMethodForDesugaring(invoke, context);
     assert methodForDesugaring != null;
+    // Specific apis that we never want to outline, namely, apis for stack introspection since it
+    // confuses developers in debug mode.
+    if (appView
+        .options()
+        .machineDesugaredLibrarySpecification
+        .getNeverOutlineApi()
+        .contains(methodForDesugaring.getReference())) {
+      return false;
+    }
     return methodForDesugaring.getAccessFlags().isPublic();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
index 0e6bd6a..94bd06b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
@@ -586,6 +586,11 @@
                 appView.dexItemFactory().createProto(newReturnType, newParameterTypes),
                 method.name);
     assert convertedAPI == methodWithVivifiedTypeInSignature(method, newHolder, appView)
+        || appView
+            .options()
+            .machineDesugaredLibrarySpecification
+            .getApiGenericConversion()
+            .containsKey(method)
         || invalidType(method, returnConversion, parameterConversions, appView) != null;
     return convertedAPI;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
index 2dcf7a4..178255b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.ClasspathMethod;
 import com.android.tools.r8.graph.ClasspathOrLibraryClass;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexClass;
@@ -31,7 +32,6 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.CustomConversionDescriptor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.WrapperDescriptor;
 import com.android.tools.r8.ir.synthetic.apiconverter.NullableConversionCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.apiconverter.NullableConversionCfCodeProvider.ArrayConversionCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.apiconverter.WrapperConstructorCfCodeProvider;
@@ -168,6 +168,28 @@
     return false;
   }
 
+  private DexMethod ensureApiGenericConversion(
+      DexMethod conversion, DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer) {
+    assert !appView.options().isDesugaredLibraryCompilation();
+    ClasspathMethod classpathMethod =
+        appView
+            .getSyntheticItems()
+            .ensureFixedClasspathMethodFromType(
+                conversion.getName(),
+                conversion.getProto(),
+                kinds -> kinds.GENERIC_API_CONVERSION_STUB,
+                conversion.getHolderType(),
+                appView,
+                ignored -> {},
+                eventConsumer::acceptGenericApiConversionStub,
+                methodBuilder ->
+                    methodBuilder
+                        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                        .setCode(null));
+    assert classpathMethod.getReference() == conversion;
+    return conversion;
+  }
+
   public DexMethod ensureConversionMethod(
       DexType type,
       boolean destIsVivified,
@@ -176,7 +198,7 @@
       Supplier<UniqueContext> contextSupplier) {
     if (apiGenericTypesConversion != null) {
       assert !type.isArrayType();
-      return apiGenericTypesConversion;
+      return ensureApiGenericConversion(apiGenericTypesConversion, eventConsumer);
     }
     DexType srcType = destIsVivified ? type : vivifiedTypeFor(type);
     DexType destType = destIsVivified ? vivifiedTypeFor(type) : type;
@@ -727,22 +749,8 @@
   }
 
   private DexClass getWrapperContext(DexClass context, WrapperKind kind) {
-    if (kind != WrapperKind.VIVIFIED_WRAPPER) {
-      return context;
-    }
-    WrapperDescriptor descriptor =
-        appView.options().machineDesugaredLibrarySpecification.getWrappers().get(context.type);
-    assert descriptor != null;
-    if (descriptor.hasNonPublicAccess()) {
-      return appView
-          .getSyntheticItems()
-          .ensureFixedClasspathClassFromType(
-              kinds -> kinds.VIVIFIED,
-              vivifiedTypeFor(context.type),
-              appView,
-              ignored -> {},
-              ignored -> {});
-    }
+    // A different context can be specified here, so that the wrapper is prefixed by a different
+    // class than the context.
     return context;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizerEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizerEventConsumer.java
index 6d91ba2..4adc11f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizerEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizerEventConsumer.java
@@ -26,6 +26,8 @@
     void acceptWrapperClasspathClass(DexClasspathClass clazz);
 
     void acceptEnumConversionClasspathClass(DexClasspathClass clazz);
+
+    void acceptGenericApiConversionStub(DexClasspathClass dexClasspathClass);
   }
 
   interface DesugaredLibraryAPIConverterEventConsumer
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java
index 49cc5f6..325cdb0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.Timing;
 import java.util.List;
+import java.util.Set;
 
 public class HumanDesugaredLibrarySpecification implements DesugaredLibrarySpecification {
 
@@ -73,6 +74,11 @@
   }
 
   @Override
+  public Set<String> getMaintainTypeOrPrefixForTesting() {
+    return rewritingFlags.getMaintainPrefix();
+  }
+
+  @Override
   public List<String> getExtraKeepRules() {
     return topLevelFlags.getExtraKeepRules();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
index 0be57c5..16c578b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
@@ -56,6 +56,7 @@
   static final String DONT_REWRITE_PREFIX_KEY = "dont_rewrite_prefix";
   static final String MAINTAIN_PREFIX_KEY = "maintain_prefix";
   static final String RETARGET_STATIC_FIELD_KEY = "retarget_static_field";
+  static final String NEVER_OUTLINE_API_KEY = "never_outline_api";
   static final String COVARIANT_RETARGET_METHOD_KEY = "covariant_retarget_method";
   static final String RETARGET_METHOD_KEY = "retarget_method";
   static final String RETARGET_METHOD_EMULATED_DISPATCH_KEY =
@@ -264,6 +265,11 @@
         builder.putDontRewritePrefix(dontRewritePrefix.getAsString());
       }
     }
+    if (jsonFlagSet.has(NEVER_OUTLINE_API_KEY)) {
+      for (JsonElement neverOutlineApi : jsonFlagSet.get(NEVER_OUTLINE_API_KEY).getAsJsonArray()) {
+        builder.neverOutlineApi(parseMethod(neverOutlineApi.getAsString()));
+      }
+    }
     if (jsonFlagSet.has(API_GENERIC_TYPES_CONVERSION)) {
       for (Map.Entry<String, JsonElement> methodAndDescription :
           jsonFlagSet.get(API_GENERIC_TYPES_CONVERSION).getAsJsonObject().entrySet()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
index a927cdf..8c4316f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
@@ -40,6 +40,7 @@
   private final Set<DexMethod> dontRewriteInvocation;
   private final Set<DexType> dontRetarget;
   private final Map<DexType, Set<DexMethod>> wrapperConversions;
+  private final Set<DexMethod> neverOutlineApi;
   private final Map<DexMethod, MethodAccessFlags> amendLibraryMethod;
   private final Map<DexField, FieldAccessFlags> amendLibraryField;
 
@@ -59,6 +60,7 @@
       Set<DexMethod> dontRewriteInvocation,
       Set<DexType> dontRetarget,
       Map<DexType, Set<DexMethod>> wrapperConversion,
+      Set<DexMethod> neverOutlineApi,
       Map<DexMethod, MethodAccessFlags> amendLibraryMethod,
       Map<DexField, FieldAccessFlags> amendLibraryField) {
     this.rewritePrefix = rewritePrefix;
@@ -76,6 +78,7 @@
     this.dontRewriteInvocation = dontRewriteInvocation;
     this.dontRetarget = dontRetarget;
     this.wrapperConversions = wrapperConversion;
+    this.neverOutlineApi = neverOutlineApi;
     this.amendLibraryMethod = amendLibraryMethod;
     this.amendLibraryField = amendLibraryField;
   }
@@ -97,6 +100,7 @@
         ImmutableSet.of(),
         ImmutableSet.of(),
         ImmutableMap.of(),
+        ImmutableSet.of(),
         ImmutableMap.of(),
         ImmutableMap.of());
   }
@@ -124,6 +128,7 @@
         dontRewriteInvocation,
         dontRetarget,
         wrapperConversions,
+        neverOutlineApi,
         amendLibraryMethod,
         amendLibraryField);
   }
@@ -164,6 +169,10 @@
     return retargetMethodEmulatedDispatch;
   }
 
+  public Set<DexMethod> getNeverOutlineApi() {
+    return neverOutlineApi;
+  }
+
   public Map<DexMethod, DexMethod[]> getApiGenericConversion() {
     return apiGenericTypesConversion;
   }
@@ -227,6 +236,7 @@
     private final Set<DexMethod> dontRewriteInvocation;
     private final Set<DexType> dontRetarget;
     private final Map<DexType, Set<DexMethod>> wrapperConversions;
+    private final Set<DexMethod> neverOutlineApi;
     private final Map<DexMethod, MethodAccessFlags> amendLibraryMethod;
     private final Map<DexField, FieldAccessFlags> amendLibraryField;
 
@@ -249,6 +259,7 @@
           Sets.newIdentityHashSet(),
           Sets.newIdentityHashSet(),
           new IdentityHashMap<>(),
+          Sets.newIdentityHashSet(),
           new IdentityHashMap<>(),
           new IdentityHashMap<>());
     }
@@ -271,6 +282,7 @@
         Set<DexMethod> dontRewriteInvocation,
         Set<DexType> dontRetargetLibMember,
         Map<DexType, Set<DexMethod>> wrapperConversions,
+        Set<DexMethod> neverOutlineApi,
         Map<DexMethod, MethodAccessFlags> amendLibraryMethod,
         Map<DexField, FieldAccessFlags> amendLibraryField) {
       this.reporter = reporter;
@@ -292,6 +304,8 @@
       this.dontRetarget = Sets.newIdentityHashSet();
       this.dontRetarget.addAll(dontRetargetLibMember);
       this.wrapperConversions = new IdentityHashMap<>(wrapperConversions);
+      this.neverOutlineApi = Sets.newIdentityHashSet();
+      this.neverOutlineApi.addAll(neverOutlineApi);
       this.amendLibraryMethod = new IdentityHashMap<>(amendLibraryMethod);
       this.amendLibraryField = new IdentityHashMap<>(amendLibraryField);
     }
@@ -439,6 +453,11 @@
       return this;
     }
 
+    public Builder neverOutlineApi(DexMethod method) {
+      neverOutlineApi.add(method);
+      return this;
+    }
+
     public Builder amendLibraryField(DexField member, FieldAccessFlags flags) {
       amendLibraryField.put(member, flags);
       return this;
@@ -462,6 +481,7 @@
           ImmutableSet.copyOf(dontRewriteInvocation),
           ImmutableSet.copyOf(dontRetarget),
           ImmutableMap.copyOf(wrapperConversions),
+          ImmutableSet.copyOf(neverOutlineApi),
           ImmutableMap.copyOf(amendLibraryMethod),
           ImmutableMap.copyOf(amendLibraryField));
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java
index cd236c0..3507445 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.LegacyToHumanSpecificationConverter;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
 import java.util.List;
 import java.util.Map;
@@ -111,6 +112,11 @@
   }
 
   @Override
+  public Set<String> getMaintainTypeOrPrefixForTesting() {
+    return ImmutableSet.of();
+  }
+
+  @Override
   public String getJsonSource() {
     return topLevelFlags.getJsonSource();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
index 3de3ac0..e93c6c6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
@@ -143,7 +143,10 @@
   }
 
   private static Path getAndroidJarPath(AndroidApiLevel apiLevel) {
-    String jar = String.format(ANDROID_JAR_PATTERN, apiLevel.getLevel());
+    String jar =
+        apiLevel == AndroidApiLevel.MASTER
+            ? "third_party/android_jar/lib-master/android.jar"
+            : String.format(ANDROID_JAR_PATTERN, apiLevel.getLevel());
     return Paths.get(jar);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
index 1b83bf8..dc24bff 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
@@ -21,6 +21,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 public class MachineDesugaredLibrarySpecification implements DesugaredLibrarySpecification {
 
@@ -103,6 +104,13 @@
     return topLevelFlags.getExtraKeepRules();
   }
 
+  @Override
+  public Set<String> getMaintainTypeOrPrefixForTesting() {
+    return rewritingFlags.getMaintainType().stream()
+        .map(DexType::toString)
+        .collect(Collectors.toSet());
+  }
+
   public Map<DexType, DexType> getRewriteType() {
     return rewritingFlags.getRewriteType();
   }
@@ -180,6 +188,10 @@
     return rewritingFlags.getCustomConversions();
   }
 
+  public Set<DexMethod> getNeverOutlineApi() {
+    return rewritingFlags.getNeverOutlineApi();
+  }
+
   public Map<DexMethod, MethodAccessFlags> getAmendLibraryMethods() {
     return rewritingFlags.getAmendLibraryMethod();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
index 523042f..6d3f489 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
@@ -42,6 +42,7 @@
       Map<DexType, DexType> legacyBackport,
       Set<DexType> dontRetarget,
       Map<DexType, CustomConversionDescriptor> customConversions,
+      Set<DexMethod> neverOutlineApi,
       Map<DexMethod, MethodAccessFlags> amendLibraryMethods,
       Map<DexField, FieldAccessFlags> amendLibraryFields) {
     this.rewriteType = rewriteType;
@@ -60,6 +61,7 @@
     this.legacyBackport = legacyBackport;
     this.dontRetarget = dontRetarget;
     this.customConversions = customConversions;
+    this.neverOutlineApi = neverOutlineApi;
     this.amendLibraryMethod = amendLibraryMethods;
     this.amendLibraryField = amendLibraryFields;
   }
@@ -106,6 +108,7 @@
   private final Map<DexType, DexType> legacyBackport;
   private final Set<DexType> dontRetarget;
   private final Map<DexType, CustomConversionDescriptor> customConversions;
+  private final Set<DexMethod> neverOutlineApi;
   private final Map<DexMethod, MethodAccessFlags> amendLibraryMethod;
   private final Map<DexField, FieldAccessFlags> amendLibraryField;
 
@@ -183,6 +186,10 @@
     return customConversions;
   }
 
+  public Set<DexMethod> getNeverOutlineApi() {
+    return neverOutlineApi;
+  }
+
   public Map<DexMethod, MethodAccessFlags> getAmendLibraryMethod() {
     return amendLibraryMethod;
   }
@@ -253,6 +260,7 @@
     private final ImmutableSet.Builder<DexType> dontRetarget = ImmutableSet.builder();
     private final ImmutableMap.Builder<DexType, CustomConversionDescriptor> customConversions =
         ImmutableMap.builder();
+    private final ImmutableSet.Builder<DexMethod> neverOutlineApi = ImmutableSet.builder();
     private final ImmutableMap.Builder<DexMethod, MethodAccessFlags> amendLibraryMethod =
         ImmutableMap.builder();
     private final ImmutableMap.Builder<DexField, FieldAccessFlags> amendLibraryField =
@@ -311,6 +319,10 @@
       this.wrappers.put(type, descriptor);
     }
 
+    public void neverOutlineApi(DexMethod method) {
+      neverOutlineApi.add(method);
+    }
+
     public void putLegacyBackport(DexType src, DexType target) {
       legacyBackport.put(src, target);
     }
@@ -370,6 +382,7 @@
           legacyBackport.build(),
           dontRetarget.build(),
           customConversions.build(),
+          neverOutlineApi.build(),
           amendLibraryMethod.build(),
           amendLibraryField.build());
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/DesugaredLibraryConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/DesugaredLibraryConverter.java
new file mode 100644
index 0000000..afddbae
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/DesugaredLibraryConverter.java
@@ -0,0 +1,138 @@
+// 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.desugaredlibrary.specificationconversion;
+
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser.isHumanSpecification;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser.isMachineSpecification;
+
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.StringResource.FileResource;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecificationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.MultiAPILevelLegacyDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.MultiAPILevelLegacyDesugaredLibrarySpecificationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MultiAPILevelMachineDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableSet;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+
+public class DesugaredLibraryConverter {
+
+  public static void main(String[] args) throws IOException {
+    Path jsonFile = Paths.get(args[0]);
+    Path desugaredLibraryJar = Paths.get(args[1]);
+    Path androidJar = Paths.get(args[2]);
+    Path output = Paths.get(args[3]);
+    convertMultiLevelAnythingToMachineSpecification(
+        jsonFile, ImmutableSet.of(desugaredLibraryJar), ImmutableSet.of(androidJar), output);
+  }
+
+  public static void convertMultiLevelAnythingToMachineSpecification(
+      Path jsonSpec, Set<Path> desugaredLibraryFiles, Set<Path> libraryFiles, Path output)
+      throws IOException {
+
+    InternalOptions options = new InternalOptions();
+
+    FileResource jsonResource = StringResource.fromFile(jsonSpec);
+    JsonObject jsonConfig = parseJsonConfig(options, jsonResource);
+
+    if (isMachineSpecification(jsonConfig, options.reporter, jsonResource.getOrigin())) {
+      // Nothing to convert;
+      Files.copy(jsonSpec, output);
+      return;
+    }
+
+    DexApplication appForConversion =
+        getAppForConversion(options, libraryFiles, desugaredLibraryFiles);
+    MultiAPILevelHumanDesugaredLibrarySpecification humanSpec =
+        getInputAsHumanSpecification(options, jsonResource, jsonConfig, appForConversion);
+    String outputString = convertToMachineSpecification(options, appForConversion, humanSpec);
+
+    Files.write(output, Collections.singleton(outputString));
+  }
+
+  private static String convertToMachineSpecification(
+      InternalOptions options,
+      DexApplication appForConversion,
+      MultiAPILevelHumanDesugaredLibrarySpecification humanSpec)
+      throws IOException {
+    HumanToMachineSpecificationConverter converter =
+        new HumanToMachineSpecificationConverter(Timing.empty());
+    MultiAPILevelMachineDesugaredLibrarySpecification machineSpec =
+        converter.convertAllAPILevels(humanSpec, appForConversion);
+    Box<String> machineJson = new Box<>();
+    MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.export(
+        machineSpec, (string, handler) -> machineJson.set(string), options.dexItemFactory());
+    return machineJson.get();
+  }
+
+  private static JsonObject parseJsonConfig(InternalOptions options, FileResource jsonResource) {
+    JsonObject jsonConfig;
+    try {
+      String jsonConfigString = jsonResource.getString();
+      JsonParser parser = new JsonParser();
+      jsonConfig = parser.parse(jsonConfigString).getAsJsonObject();
+    } catch (Exception e) {
+      throw options.reporter.fatalError(new ExceptionDiagnostic(e, jsonResource.getOrigin()));
+    }
+    return jsonConfig;
+  }
+
+  /**
+   * Parse the human specification, or parse and convert the legacy specification into human
+   * specification.
+   */
+  private static MultiAPILevelHumanDesugaredLibrarySpecification getInputAsHumanSpecification(
+      InternalOptions options,
+      FileResource jsonResource,
+      JsonObject jsonConfig,
+      DexApplication appForConversion)
+      throws IOException {
+    if (!isHumanSpecification(jsonConfig, options.reporter, jsonResource.getOrigin())) {
+      MultiAPILevelLegacyDesugaredLibrarySpecification legacySpec =
+          new MultiAPILevelLegacyDesugaredLibrarySpecificationParser(
+                  options.dexItemFactory(), options.reporter)
+              .parseMultiLevelConfiguration(jsonResource);
+
+      LegacyToHumanSpecificationConverter converter =
+          new LegacyToHumanSpecificationConverter(Timing.empty());
+
+      return converter.convertAllAPILevels(legacySpec, appForConversion);
+    }
+    return new MultiAPILevelHumanDesugaredLibrarySpecificationParser(
+            options.dexItemFactory(), options.reporter)
+        .parseMultiLevelConfiguration(jsonResource);
+  }
+
+  public static DexApplication getAppForConversion(
+      InternalOptions options, Set<Path> androidJar, Set<Path> desugaredlibJar) throws IOException {
+    AndroidApp.Builder builder = AndroidApp.builder();
+    builder.addProgramFiles(desugaredlibJar);
+    AndroidApp inputApp = builder.addLibraryFiles(androidJar).build();
+    ApplicationReader applicationReader = new ApplicationReader(inputApp, options, Timing.empty());
+    ExecutorService executorService = ThreadUtils.getExecutorService(options);
+    assert !options.ignoreJavaLibraryOverride;
+    options.ignoreJavaLibraryOverride = true;
+    DexApplication app = applicationReader.read(executorService);
+    options.ignoreJavaLibraryOverride = false;
+    return app;
+  }
+}
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 51262ef..520533b 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
@@ -55,6 +55,7 @@
     rewriteValues(rewritingFlags.getCustomConversions());
     rewriteEmulatedInterface(rewritingFlags.getEmulatedInterfaces());
     rewriteRetargetKeys(rewritingFlags.getRetargetMethodEmulatedDispatch());
+    rewriteApiConversions(rewritingFlags.getApiGenericConversion());
     warnIfUnusedPrefix(warnConsumer);
   }
 
@@ -85,6 +86,17 @@
     }
   }
 
+  private void rewriteApiConversions(Map<DexMethod, DexMethod[]> apiGenericConversions) {
+    apiGenericConversions.forEach(
+        (k, v) -> {
+          for (DexMethod dexMethod : v) {
+            if (dexMethod != null) {
+              registerClassType(dexMethod.getHolderType());
+            }
+          }
+        });
+  }
+
   private void rewriteEmulatedInterface(Map<DexType, DexType> emulateLibraryInterface) {
     emulateLibraryInterface.forEach(builder::rewriteDerivedTypeOnly);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
index eb333a7..2773de6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
@@ -129,6 +129,7 @@
     rewritingFlags.getAmendLibraryMethod().forEach(builder::amendLibraryMethod);
     rewritingFlags.getAmendLibraryField().forEach(builder::amendLibraryField);
     rewritingFlags.getApiGenericConversion().forEach(builder::addApiGenericTypesConversion);
+    rewritingFlags.getNeverOutlineApi().forEach(builder::neverOutlineApi);
     new HumanToMachineRetargetConverter(appInfo)
         .convertRetargetFlags(rewritingFlags, builder, this::warnMissingReferences);
     new HumanToMachineEmulatedInterfaceConverter(appInfo)
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/L8InnerOuterAttributeEraser.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/L8InnerOuterAttributeEraser.java
new file mode 100644
index 0000000..4030b3b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/L8InnerOuterAttributeEraser.java
@@ -0,0 +1,66 @@
+// 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.itf;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import java.util.ArrayList;
+
+/**
+ * If D8/R8 rewrites a type but not its enclosing/inner types, then the package does not match
+ * between the types and the inner/outer class attributes cannot be maintained nor be valid anymore.
+ * This overrides keep rules regarding inner/outer attributes, and should be applied only to the L8
+ * compilation itself.
+ */
+public class L8InnerOuterAttributeEraser {
+
+  private final AppView<?> appView;
+
+  public L8InnerOuterAttributeEraser(AppView<?> appView) {
+    this.appView = appView;
+  }
+
+  public void run() {
+    assert appView.options().isDesugaredLibraryCompilation();
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      eraseInvalidAttributes(clazz);
+    }
+  }
+
+  private void eraseInvalidAttributes(DexProgramClass clazz) {
+    boolean rewritten = hasRewrittenType(clazz.type);
+
+    if (clazz.getEnclosingMethodAttribute() != null) {
+      DexType outerType = clazz.getEnclosingMethodAttribute().getEnclosingClass();
+      if (outerType != null && hasRewrittenType(outerType) != rewritten) {
+        clazz.clearEnclosingMethodAttribute();
+      }
+      // Erasing enclosing method attributes (method, not class) does not seem to make any
+      // difference at this point. It can be added here if relevant.
+    }
+
+    if (!clazz.getInnerClasses().isEmpty()) {
+      ArrayList<InnerClassAttribute> innerClasses = new ArrayList<>();
+      for (InnerClassAttribute innerClass : clazz.getInnerClasses()) {
+        if (hasRewrittenType(innerClass.getInner()) == rewritten) {
+          innerClasses.add(innerClass);
+        }
+      }
+      if (innerClasses.size() != clazz.getInnerClasses().size()) {
+        clazz.setInnerClasses(innerClasses);
+      }
+    }
+  }
+
+  private boolean hasRewrittenType(DexType type) {
+    return appView
+        .options()
+        .machineDesugaredLibrarySpecification
+        .getRewriteType()
+        .containsKey(type);
+  }
+}
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 d3d0ece..ac4c0ef 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
@@ -11,7 +11,7 @@
 import com.android.tools.r8.dex.code.DexMoveResultObject;
 import com.android.tools.r8.dex.code.DexMoveResultWide;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.features.ClassToFeatureSplitMap;
+import com.android.tools.r8.features.FeatureSplitBoundaryOptimizationUtils;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -163,13 +163,9 @@
       return false;
     }
 
-    ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
-    if (!classToFeatureSplitMap.isInSameFeatureOrBothInSameBase(singleTarget, method, appView)) {
-      // Still allow inlining if we inline from the base into a feature.
-      if (!classToFeatureSplitMap.isInBase(singleTarget.getHolder(), appView)) {
-        whyAreYouNotInliningReporter.reportInliningAcrossFeatureSplit();
-        return false;
-      }
+    if (!FeatureSplitBoundaryOptimizationUtils.isSafeForInlining(method, singleTarget, appView)) {
+      whyAreYouNotInliningReporter.reportInliningAcrossFeatureSplit();
+      return false;
     }
 
     Set<Reason> validInliningReasons = appView.testing().validInliningReasons;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 96b79b1..6dc28aa 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.features.FeatureSplitBoundaryOptimizationUtils;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -382,14 +383,13 @@
       // This will fail at runtime.
       return ConstraintWithTarget.NEVER;
     }
-    if (!appView
-        .appInfo()
-        .getClassToFeatureSplitMap()
-        .isInBaseOrSameFeatureAs(
-            resolvedMember.getHolderType(), context.asProgramMethod(), appView)) {
-      // We never inline into the base from a feature (calls should never happen) and we
-      // never inline between features, so this check should be sufficient.
-      return ConstraintWithTarget.NEVER;
+    ConstraintWithTarget featureSplitInliningConstraint =
+        FeatureSplitBoundaryOptimizationUtils.getInliningConstraintForResolvedMember(
+            context, resolvedMember, appView);
+    assert featureSplitInliningConstraint == ConstraintWithTarget.ALWAYS
+        || featureSplitInliningConstraint == ConstraintWithTarget.NEVER;
+    if (featureSplitInliningConstraint == ConstraintWithTarget.NEVER) {
+      return featureSplitInliningConstraint;
     }
     DexType resolvedHolder = graphLens.lookupType(resolvedMember.getHolderType());
     assert initialResolutionHolder != null;
diff --git a/src/main/java/com/android/tools/r8/lightir/ByteUtils.java b/src/main/java/com/android/tools/r8/lightir/ByteUtils.java
index a4a4bdf..5c51959 100644
--- a/src/main/java/com/android/tools/r8/lightir/ByteUtils.java
+++ b/src/main/java/com/android/tools/r8/lightir/ByteUtils.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.lightir;
 
+import it.unimi.dsi.fastutil.bytes.ByteIterator;
+
 /** Simple utilities for byte encodings. */
 public class ByteUtils {
 
@@ -36,6 +38,15 @@
     writer.put(truncateToU1(value));
   }
 
+  public static int readEncodedInt(ByteIterator iterator) {
+    assert 4 == intEncodingSize(0);
+    int value = ensureU1(iterator.nextByte()) << 24;
+    value |= ensureU1(iterator.nextByte()) << 16;
+    value |= ensureU1(iterator.nextByte()) << 8;
+    value |= ensureU1(iterator.nextByte());
+    return value;
+  }
+
   public static boolean isU2(int value) {
     return (value >= 0) && (value <= 0xFFFF);
   }
diff --git a/src/main/java/com/android/tools/r8/lightir/IR2LIRConverter.java b/src/main/java/com/android/tools/r8/lightir/IR2LIRConverter.java
new file mode 100644
index 0000000..e91fd8a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/IR2LIRConverter.java
@@ -0,0 +1,45 @@
+// 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.lightir;
+
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+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.Value;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+
+public class IR2LIRConverter {
+
+  private IR2LIRConverter() {}
+
+  public static LIRCode translate(IRCode irCode) {
+    Reference2IntMap<Value> values = new Reference2IntOpenHashMap<>();
+    int index = 0;
+    for (Instruction instruction : irCode.instructions()) {
+      if (instruction.hasOutValue()) {
+        values.put(instruction.outValue(), index);
+      }
+      index++;
+    }
+    LIRBuilder<Value> builder =
+        new LIRBuilder<Value>(irCode.context().getReference(), values::getInt)
+            .setMetadata(irCode.metadata());
+    BasicBlockIterator blockIt = irCode.listIterator();
+    while (blockIt.hasNext()) {
+      BasicBlock block = blockIt.next();
+      // TODO(b/225838009): Support control flow.
+      assert !block.hasPhis();
+      InstructionIterator it = block.iterator();
+      while (it.hasNext()) {
+        Instruction instruction = it.next();
+        builder.setCurrentPosition(instruction.getPosition());
+        instruction.buildLIR(builder);
+      }
+    }
+    return builder.build();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIR2IRConverter.java b/src/main/java/com/android/tools/r8/lightir/LIR2IRConverter.java
new file mode 100644
index 0000000..75a372b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/LIR2IRConverter.java
@@ -0,0 +1,237 @@
+// 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.lightir;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.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.BasicBlock;
+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.IRCode;
+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.NumberGenerator;
+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;
+import com.android.tools.r8.ir.code.StaticGet;
+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 it.unimi.dsi.fastutil.ints.IntList;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+public class LIR2IRConverter {
+
+  private LIR2IRConverter() {}
+
+  public static IRCode translate(ProgramMethod method, LIRCode lirCode, AppView<?> appView) {
+    Parser parser = new Parser(lirCode, method.getReference(), appView);
+    parser.parseArguments(method);
+    lirCode.forEach(view -> view.accept(parser));
+    return parser.getIRCode(method);
+  }
+
+  /**
+   * When building IR the structured LIR parser is used to obtain the decoded operand indexes. The
+   * below parser subclass handles translation of indexes to SSA values.
+   */
+  private static class Parser extends LIRParsedInstructionCallback {
+
+    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 BasicBlock currentBlock = null;
+    private int nextInstructionIndex = 0;
+
+    private Position currentPosition;
+    private PositionEntry nextPositionEntry = null;
+    private int nextIndexInPositionsTable = 0;
+
+    public Parser(LIRCode code, DexMethod method, AppView<?> appView) {
+      super(code);
+      assert code.getPositionTable().length > 0;
+      assert code.getPositionTable()[0].fromInstructionIndex == 0;
+      this.appView = appView;
+      this.code = code;
+      values = new Value[code.getArgumentCount() + code.getInstructionCount()];
+      // Recreate the preamble position. This is active for arguments and code with no positions.
+      currentPosition = SyntheticPosition.builder().setLine(0).setMethod(method).build();
+    }
+
+    private void ensureCurrentPosition() {
+      if (nextPositionEntry != null
+          && nextPositionEntry.fromInstructionIndex < nextInstructionIndex) {
+        currentPosition = nextPositionEntry.position;
+        advanceNextPositionEntry();
+      }
+    }
+
+    private void advanceNextPositionEntry() {
+      nextPositionEntry =
+          nextIndexInPositionsTable < code.getPositionTable().length
+              ? code.getPositionTable()[nextIndexInPositionsTable++]
+              : null;
+    }
+
+    public void parseArguments(ProgramMethod method) {
+      currentBlock = new BasicBlock();
+      currentBlock.setNumber(basicBlockNumberGenerator.next());
+      boolean hasReceiverArgument = !method.getDefinition().isStatic();
+      assert code.getArgumentCount()
+          == method.getParameters().size() + (hasReceiverArgument ? 1 : 0);
+      if (hasReceiverArgument) {
+        addThisArgument(method.getHolderType());
+      }
+      method.getParameters().forEach(this::addArgument);
+      // Set up position state after adding arguments.
+      advanceNextPositionEntry();
+    }
+
+    public IRCode getIRCode(ProgramMethod method) {
+      // TODO(b/225838009): Support control flow.
+      currentBlock.setFilled();
+      blocks.add(currentBlock);
+      return new IRCode(
+          appView.options(),
+          method,
+          Position.syntheticNone(),
+          blocks,
+          valueNumberGenerator,
+          basicBlockNumberGenerator,
+          code.getMetadata(),
+          method.getOrigin(),
+          new MutableMethodConversionOptions(appView.options()));
+    }
+
+    public Value getSsaValue(int index) {
+      Value value = values[index];
+      if (value == null) {
+        value = new Value(valueNumberGenerator.next(), TypeElement.getBottom(), null);
+        values[index] = value;
+      }
+      return value;
+    }
+
+    public List<Value> getSsaValues(IntList indices) {
+      List<Value> arguments = new ArrayList<>(indices.size());
+      for (int i = 0; i < indices.size(); i++) {
+        arguments.add(getSsaValue(indices.getInt(i)));
+      }
+      return arguments;
+    }
+
+    public int peekNextInstructionIndex() {
+      return nextInstructionIndex;
+    }
+
+    public Value getOutValueForNextInstruction(TypeElement type) {
+      // TODO(b/225838009): Support debug locals.
+      DebugLocalInfo localInfo = null;
+      int index = peekNextInstructionIndex();
+      Value value = values[index];
+      if (value == null) {
+        value = new Value(valueNumberGenerator.next(), type, localInfo);
+        values[index] = value;
+      } else {
+        value.setType(type);
+        if (localInfo != null) {
+          value.setLocalInfo(localInfo);
+        }
+      }
+      return value;
+    }
+
+    private void addInstruction(Instruction instruction) {
+      ensureCurrentPosition();
+      instruction.setPosition(currentPosition);
+      currentBlock.getInstructions().add(instruction);
+      instruction.setBlock(currentBlock);
+      ++nextInstructionIndex;
+    }
+
+    private void addThisArgument(DexType type) {
+      Argument argument = addArgument(type);
+      argument.outValue().markAsThis();
+    }
+
+    private Argument addArgument(DexType type) {
+      Argument instruction =
+          new Argument(
+              getOutValueForNextInstruction(type.toTypeElement(appView)),
+              peekNextInstructionIndex(),
+              type.isBooleanType());
+      addInstruction(instruction);
+      return instruction;
+    }
+
+    @Override
+    public void onConstNull() {
+      Value dest = getOutValueForNextInstruction(TypeElement.getNull());
+      addInstruction(new ConstNumber(dest, 0));
+    }
+
+    @Override
+    public void onConstString(DexString string) {
+      Value dest = getOutValueForNextInstruction(TypeElement.stringClassType(appView));
+      addInstruction(new ConstString(dest, string));
+    }
+
+    @Override
+    public void onInvokeDirect(DexMethod target, IntList arguments) {
+      // TODO(b/225838009): Maintain is-interface bit.
+      Value dest = getInvokeInstructionOutputValue(target);
+      List<Value> ssaArgumentValues = getSsaValues(arguments);
+      InvokeDirect instruction = new InvokeDirect(target, dest, ssaArgumentValues);
+      addInstruction(instruction);
+    }
+
+    @Override
+    public void onInvokeVirtual(DexMethod target, IntList arguments) {
+      // TODO(b/225838009): Maintain is-interface bit.
+      Value dest = getInvokeInstructionOutputValue(target);
+      List<Value> ssaArgumentValues = getSsaValues(arguments);
+      InvokeVirtual instruction = new InvokeVirtual(target, dest, ssaArgumentValues);
+      addInstruction(instruction);
+    }
+
+    private Value getInvokeInstructionOutputValue(DexMethod target) {
+      return target.getReturnType().isVoidType()
+          ? null
+          : getOutValueForNextInstruction(target.getReturnType().toTypeElement(appView));
+    }
+
+    @Override
+    public void onStaticGet(DexField field) {
+      Value dest = getOutValueForNextInstruction(field.getTypeElement(appView));
+      addInstruction(new StaticGet(dest, field));
+    }
+
+    @Override
+    public void onReturnVoid() {
+      addInstruction(new Return());
+    }
+
+    @Override
+    public void onDebugPosition() {
+      addInstruction(new DebugPosition());
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRBasicInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LIRBasicInstructionCallback.java
deleted file mode 100644
index a08c036..0000000
--- a/src/main/java/com/android/tools/r8/lightir/LIRBasicInstructionCallback.java
+++ /dev/null
@@ -1,18 +0,0 @@
-// 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.lightir;
-
-public interface LIRBasicInstructionCallback {
-
-  /**
-   * Most basic callback for interpreting LIR.
-   *
-   * @param opcode The opcode of the instruction (See {@code LIROpcodes} for values).
-   * @param operandsOffsetInBytes The offset into the byte stream at which the instruction's payload
-   *     starts.
-   * @param operandsSizeInBytes The total size of the instruction's payload (excluding the opcode
-   *     itself an any payload size encoding).
-   */
-  void onInstruction(int opcode, int operandsOffsetInBytes, int operandsSizeInBytes);
-}
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 7ced9ef..fe9e5b2 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
@@ -3,18 +3,64 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.lightir;
 
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItem;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.ir.code.IRMetadata;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
+import com.android.tools.r8.lightir.LIRCode.PositionEntry;
+import com.android.tools.r8.utils.ListUtils;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.util.ArrayList;
+import java.util.List;
 
-public class LIRBuilder {
+/**
+ * Builder for constructing LIR code from IR.
+ *
+ * @param <V> Type of SSA values. This is abstract to ensure that value internals are not used in
+ *     building.
+ */
+public class LIRBuilder<V> {
+
+  public interface ValueIndexGetter<V> {
+    int getValueIndex(V value);
+  }
 
   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 List<PositionEntry> positionTable;
+  private int argumentCount = 0;
+  private int instructionCount = 0;
+  private IRMetadata metadata = null;
 
-  public LIRBuilder() {
+  private Position currentPosition;
+  private Position flushedPosition;
+
+  public LIRBuilder(DexMethod method, ValueIndexGetter<V> valueIndexGetter) {
     constants = new Reference2IntOpenHashMap<>();
+    positionTable = new ArrayList<>();
+    this.valueIndexGetter = valueIndexGetter;
+    currentPosition = SyntheticPosition.builder().setLine(0).setMethod(method).build();
+    flushedPosition = currentPosition;
+  }
+
+  public LIRBuilder<V> setCurrentPosition(Position position) {
+    assert position != null;
+    assert position != Position.none();
+    currentPosition = position;
+    return this;
+  }
+
+  private void setPositionIndex(int instructionIndex, Position position) {
+    assert positionTable.isEmpty()
+        || ListUtils.last(positionTable).fromInstructionIndex < instructionIndex;
+    positionTable.add(new PositionEntry(instructionIndex, position));
   }
 
   private int getConstantIndex(DexItem item) {
@@ -23,17 +69,61 @@
     return oldIndex != null ? oldIndex : nextIndex;
   }
 
-  public LIRBuilder addNop() {
-    writer.writeOneByteInstruction(LIROpcodes.NOP);
+  private int constantIndexSize(DexItem item) {
+    return 4;
+  }
+
+  private void writeConstantIndex(DexItem item) {
+    int index = getConstantIndex(item);
+    ByteUtils.writeEncodedInt(index, writer::writeOperand);
+  }
+
+  private int getValueIndex(V value) {
+    return valueIndexGetter.getValueIndex(value);
+  }
+
+  private int valueIndexSize(int index) {
+    return ByteUtils.intEncodingSize(index);
+  }
+
+  private void writeValueIndex(int index) {
+    ByteUtils.writeEncodedInt(index, writer::writeOperand);
+  }
+
+  public LIRBuilder<V> setMetadata(IRMetadata metadata) {
+    this.metadata = metadata;
     return this;
   }
 
-  public LIRBuilder addConstNull() {
+  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) {
+    // Arguments are implicitly given by method descriptor and not an actual instruction.
+    assert argumentCount == index;
+    argumentCount++;
+    return this;
+  }
+
+  private void addInstruction() {
+    if (!currentPosition.equals(flushedPosition)) {
+      setPositionIndex(instructionCount, currentPosition);
+      flushedPosition = currentPosition;
+    }
+    ++instructionCount;
+  }
+
+  public LIRBuilder<V> addConstNull() {
+    addInstruction();
     writer.writeOneByteInstruction(LIROpcodes.ACONST_NULL);
     return this;
   }
 
-  public LIRBuilder addConstInt(int value) {
+  public LIRBuilder<V> addConstInt(int value) {
+    addInstruction();
     if (0 <= value && value <= 5) {
       writer.writeOneByteInstruction(LIROpcodes.ICONST_0 + value);
     } else {
@@ -43,10 +133,70 @@
     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);
+    }
+    writer.writeInstruction(opcode, argumentOprandSize);
+    writeConstantIndex(method);
+    for (int argumentIndex : argumentIndexes) {
+      writeValueIndex(argumentIndex);
+    }
+    return this;
+  }
+
+  public LIRBuilder<V> addInvokeDirect(DexMethod method, List<V> arguments) {
+    return addInvokeInstruction(LIROpcodes.INVOKEDIRECT, method, arguments);
+  }
+
+  public LIRBuilder<V> addInvokeVirtual(DexMethod method, List<V> arguments) {
+    return addInvokeInstruction(LIROpcodes.INVOKEVIRTUAL, method, arguments);
+  }
+
+  public LIRBuilder<V> addReturn(V value) {
+    throw new Unimplemented();
+  }
+
+  public LIRBuilder<V> addReturnVoid() {
+    addInstruction();
+    writer.writeOneByteInstruction(LIROpcodes.RETURN);
+    return this;
+  }
+
+  public LIRBuilder<V> addDebugPosition(Position position) {
+    assert currentPosition == position;
+    addInstruction();
+    writer.writeOneByteInstruction(LIROpcodes.DEBUGPOS);
+    return this;
+  }
+
   public LIRCode build() {
+    assert metadata != null;
     int constantsCount = constants.size();
     DexItem[] constantTable = new DexItem[constantsCount];
     constants.forEach((item, index) -> constantTable[index] = item);
-    return new LIRCode(constantTable, byteWriter.toByteArray());
+    return new LIRCode(
+        metadata,
+        constantTable,
+        positionTable.toArray(new PositionEntry[positionTable.size()]),
+        argumentCount,
+        byteWriter.toByteArray(),
+        instructionCount);
   }
 }
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 c91dbd9..04dffd4 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRCode.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRCode.java
@@ -4,24 +4,115 @@
 package com.android.tools.r8.lightir;
 
 import com.android.tools.r8.graph.DexItem;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.IRMetadata;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.lightir.LIRBuilder.ValueIndexGetter;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.StringUtils.BraceType;
+import java.util.Arrays;
 
 public class LIRCode implements Iterable<LIRInstructionView> {
 
+  public static class PositionEntry {
+    final int fromInstructionIndex;
+    final Position position;
+
+    public PositionEntry(int fromInstructionIndex, Position position) {
+      this.fromInstructionIndex = fromInstructionIndex;
+      this.position = position;
+    }
+  }
+
+  private final IRMetadata metadata;
+
+  /** Constant pool of items. */
   private final DexItem[] constants;
+
+  private final PositionEntry[] positionTable;
+
+  /** Full number of arguments (including receiver for non-static methods). */
+  private final int argumentCount;
+
+  /** Byte encoding of the instructions (including phis). */
   private final byte[] instructions;
 
-  public static LIRBuilder builder() {
-    return new LIRBuilder();
+  /** Cached value for the number of logical instructions (including phis). */
+  private final int instructionCount;
+
+  public static <V> LIRBuilder<V> builder(DexMethod method, ValueIndexGetter<V> valueIndexGetter) {
+    return new LIRBuilder<V>(method, valueIndexGetter);
   }
 
   // Should be constructed using LIRBuilder.
-  LIRCode(DexItem[] constants, byte[] instructions) {
+  LIRCode(
+      IRMetadata metadata,
+      DexItem[] constants,
+      PositionEntry[] positions,
+      int argumentCount,
+      byte[] instructions,
+      int instructionCount) {
+    this.metadata = metadata;
     this.constants = constants;
+    this.positionTable = positions;
+    this.argumentCount = argumentCount;
     this.instructions = instructions;
+    this.instructionCount = instructionCount;
+  }
+
+  public int getArgumentCount() {
+    return argumentCount;
+  }
+
+  public byte[] getInstructionBytes() {
+    return instructions;
+  }
+
+  public int getInstructionCount() {
+    return instructionCount;
+  }
+
+  public IRMetadata getMetadata() {
+    return metadata;
+  }
+
+  public DexItem getConstantItem(int index) {
+    return constants[index];
+  }
+
+  public PositionEntry[] getPositionTable() {
+    return positionTable;
   }
 
   @Override
   public LIRIterator iterator() {
     return new LIRIterator(new ByteArrayIterator(instructions));
   }
+
+  @Override
+  public String toString() {
+    StringBuilder builder = new StringBuilder("LIRCode{");
+    builder
+        .append("args:")
+        .append(argumentCount)
+        .append(", insn(num:")
+        .append(instructionCount)
+        .append(", size:")
+        .append(instructions.length)
+        .append("):{");
+    int index = 0;
+    for (LIRInstructionView view : this) {
+      builder.append(LIROpcodes.toString(view.getOpcode()));
+      if (view.getRemainingOperandSizeInBytes() > 0) {
+        builder.append("(size:").append(1 + view.getRemainingOperandSizeInBytes()).append(")");
+      }
+      if (++index < instructionCount) {
+        builder.append(", ");
+      }
+    }
+    builder.append("}, pool(size:").append(constants.length).append("):");
+    StringUtils.append(builder, Arrays.asList(constants), ", ", BraceType.TUBORG);
+    builder.append("}");
+    return builder.toString();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LIRInstructionCallback.java
new file mode 100644
index 0000000..42a0955
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/LIRInstructionCallback.java
@@ -0,0 +1,10 @@
+// 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.lightir;
+
+/** Convenience interface to accept a LIR instruction view. */
+public interface LIRInstructionCallback {
+
+  void onInstructionView(LIRInstructionView view);
+}
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 59051e3..f42abdb 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRInstructionView.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRInstructionView.java
@@ -12,5 +12,21 @@
  */
 public interface LIRInstructionView {
 
-  void accept(LIRBasicInstructionCallback eventCallback);
+  /** Convenience method to forward control to a callback. */
+  void accept(LIRInstructionCallback eventCallback);
+
+  /** The opcode of the instruction (See {@code LIROpcodes} for values). */
+  int getOpcode();
+
+  /** The remaining size of the instruction's payload. */
+  int getRemainingOperandSizeInBytes();
+
+  /** True if the instruction has any operands that have not yet been parsed. */
+  boolean hasMoreOperands();
+
+  /** Get the next operand as a constant-pool index. */
+  int getNextConstantOperand();
+
+  /** Get the next operand as an SSA value index. */
+  int getNextValueOperand();
 }
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 e05d521..9659aa5 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRIterator.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRIterator.java
@@ -18,35 +18,69 @@
 
   private int currentByteIndex = 0;
   private int currentOpcode = -1;
-  private int currentOperandSize = 0;
+  private int endOfCurrentInstruction = 0;
 
   public LIRIterator(ByteIterator iterator) {
     this.iterator = iterator;
   }
 
+  private void skipRemainingOperands() {
+    if (hasMoreOperands()) {
+      skip(getRemainingOperandSizeInBytes());
+    }
+  }
+
   @Override
   public boolean hasNext() {
+    skipRemainingOperands();
     return iterator.hasNext();
   }
 
   @Override
   public LIRInstructionView next() {
+    skipRemainingOperands();
     currentOpcode = u1();
     if (LIROpcodes.isOneByteInstruction(currentOpcode)) {
-      currentOperandSize = 0;
+      endOfCurrentInstruction = currentByteIndex;
     } else {
       // Any instruction that is not a single byte has a two-byte header. The second byte is the
       // size of the variable width operand payload.
-      currentOperandSize = u1();
-      skip(currentOperandSize);
+      int operandSize = u1();
+      endOfCurrentInstruction = currentByteIndex + operandSize;
     }
     return this;
   }
 
   @Override
-  public void accept(LIRBasicInstructionCallback eventCallback) {
-    int operandsOffset = currentByteIndex - currentOperandSize;
-    eventCallback.onInstruction(currentOpcode, operandsOffset, currentOperandSize);
+  public void accept(LIRInstructionCallback eventCallback) {
+    eventCallback.onInstructionView(this);
+  }
+
+  @Override
+  public int getOpcode() {
+    return currentOpcode;
+  }
+
+  @Override
+  public int getRemainingOperandSizeInBytes() {
+    return endOfCurrentInstruction - currentByteIndex;
+  }
+
+  @Override
+  public boolean hasMoreOperands() {
+    return currentByteIndex < endOfCurrentInstruction;
+  }
+
+  @Override
+  public int getNextConstantOperand() {
+    assert hasMoreOperands();
+    return u4();
+  }
+
+  @Override
+  public int getNextValueOperand() {
+    assert hasMoreOperands();
+    return u4();
   }
 
   private void skip(int i) {
@@ -58,4 +92,9 @@
     ++currentByteIndex;
     return ByteUtils.fromU1(iterator.nextByte());
   }
+
+  private int u4() {
+    currentByteIndex += 4;
+    return ByteUtils.readEncodedInt(iterator);
+  }
 }
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 85cc9f1..089469f 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.lightir;
 
+import com.android.tools.r8.errors.Unreachable;
+
 /**
  * Constants related to LIR.
  *
@@ -11,12 +13,12 @@
 public interface LIROpcodes {
 
   static boolean isOneByteInstruction(int opcode) {
-    assert opcode >= NOP;
-    return opcode <= DCONST_1;
+    assert opcode >= ACONST_NULL;
+    return opcode <= DCONST_1 || opcode == RETURN || opcode == DEBUGPOS;
   }
 
   // Instructions maintaining the same opcode as defined in CF.
-  int NOP = 0;
+  // int NOP = 0;
   int ACONST_NULL = 1;
   int ICONST_M1 = 2;
   int ICONST_0 = 3;
@@ -179,4 +181,317 @@
   int LCONST = 201;
   int FCONST = 202;
   int DCONST = 203;
+  int INVOKEDIRECT = 204;
+  int DEBUGPOS = 205;
+
+  static String toString(int opcode) {
+    switch (opcode) {
+        // case NOP: return "NOP";
+      case ACONST_NULL:
+        return "ACONST_NULL";
+      case ICONST_M1:
+        return "ICONST_M1";
+      case ICONST_0:
+        return "ICONST_0";
+      case ICONST_1:
+        return "ICONST_1";
+      case ICONST_2:
+        return "ICONST_2";
+      case ICONST_3:
+        return "ICONST_3";
+      case ICONST_4:
+        return "ICONST_4";
+      case ICONST_5:
+        return "ICONST_5";
+      case LCONST_0:
+        return "LCONST_0";
+      case LCONST_1:
+        return "LCONST_1";
+      case FCONST_0:
+        return "FCONST_0";
+      case FCONST_1:
+        return "FCONST_1";
+      case FCONST_2:
+        return "FCONST_2";
+      case DCONST_0:
+        return "DCONST_0";
+      case DCONST_1:
+        return "DCONST_1";
+        // case BIPUSH: return "BIPUSH";
+        // case SIPUSH: return "SIPUSH";
+      case LDC:
+        return "LDC";
+        // case ILOAD: return "ILOAD";
+        // case LLOAD: return "LLOAD";
+        // case FLOAD: return "FLOAD";
+        // case DLOAD: return "DLOAD";
+        // case ALOAD: return "ALOAD";
+      case IALOAD:
+        return "IALOAD";
+      case LALOAD:
+        return "LALOAD";
+      case FALOAD:
+        return "FALOAD";
+      case DALOAD:
+        return "DALOAD";
+      case AALOAD:
+        return "AALOAD";
+      case BALOAD:
+        return "BALOAD";
+      case CALOAD:
+        return "CALOAD";
+      case SALOAD:
+        return "SALOAD";
+        // case ISTORE: return "ISTORE";
+        // case LSTORE: return "LSTORE";
+        // case FSTORE: return "FSTORE";
+        // case DSTORE: return "DSTORE";
+        // case ASTORE: return "ASTORE";
+      case IASTORE:
+        return "IASTORE";
+      case LASTORE:
+        return "LASTORE";
+      case FASTORE:
+        return "FASTORE";
+      case DASTORE:
+        return "DASTORE";
+      case AASTORE:
+        return "AASTORE";
+      case BASTORE:
+        return "BASTORE";
+      case CASTORE:
+        return "CASTORE";
+      case SASTORE:
+        return "SASTORE";
+        // case POP: return "POP";
+        // case POP2: return "POP2";
+        // case DUP: return "DUP";
+        // case DUP_X1: return "DUP_X1";
+        // case DUP_X2: return "DUP_X2";
+        // case DUP2: return "DUP2";
+        // case DUP2_X1: return "DUP2_X1";
+        // case DUP2_X2: return "DUP2_X2";
+        // case SWAP: return "SWAP";
+      case IADD:
+        return "IADD";
+      case LADD:
+        return "LADD";
+      case FADD:
+        return "FADD";
+      case DADD:
+        return "DADD";
+      case ISUB:
+        return "ISUB";
+      case LSUB:
+        return "LSUB";
+      case FSUB:
+        return "FSUB";
+      case DSUB:
+        return "DSUB";
+      case IMUL:
+        return "IMUL";
+      case LMUL:
+        return "LMUL";
+      case FMUL:
+        return "FMUL";
+      case DMUL:
+        return "DMUL";
+      case IDIV:
+        return "IDIV";
+      case LDIV:
+        return "LDIV";
+      case FDIV:
+        return "FDIV";
+      case DDIV:
+        return "DDIV";
+      case IREM:
+        return "IREM";
+      case LREM:
+        return "LREM";
+      case FREM:
+        return "FREM";
+      case DREM:
+        return "DREM";
+      case INEG:
+        return "INEG";
+      case LNEG:
+        return "LNEG";
+      case FNEG:
+        return "FNEG";
+      case DNEG:
+        return "DNEG";
+      case ISHL:
+        return "ISHL";
+      case LSHL:
+        return "LSHL";
+      case ISHR:
+        return "ISHR";
+      case LSHR:
+        return "LSHR";
+      case IUSHR:
+        return "IUSHR";
+      case LUSHR:
+        return "LUSHR";
+      case IAND:
+        return "IAND";
+      case LAND:
+        return "LAND";
+      case IOR:
+        return "IOR";
+      case LOR:
+        return "LOR";
+      case IXOR:
+        return "IXOR";
+      case LXOR:
+        return "LXOR";
+        // case IINC: return "IINC";
+      case I2L:
+        return "I2L";
+      case I2F:
+        return "I2F";
+      case I2D:
+        return "I2D";
+      case L2I:
+        return "L2I";
+      case L2F:
+        return "L2F";
+      case L2D:
+        return "L2D";
+      case F2I:
+        return "F2I";
+      case F2L:
+        return "F2L";
+      case F2D:
+        return "F2D";
+      case D2I:
+        return "D2I";
+      case D2L:
+        return "D2L";
+      case D2F:
+        return "D2F";
+      case I2B:
+        return "I2B";
+      case I2C:
+        return "I2C";
+      case I2S:
+        return "I2S";
+      case LCMP:
+        return "LCMP";
+      case FCMPL:
+        return "FCMPL";
+      case FCMPG:
+        return "FCMPG";
+      case DCMPL:
+        return "DCMPL";
+      case DCMPG:
+        return "DCMPG";
+      case IFEQ:
+        return "IFEQ";
+      case IFNE:
+        return "IFNE";
+      case IFLT:
+        return "IFLT";
+      case IFGE:
+        return "IFGE";
+      case IFGT:
+        return "IFGT";
+      case IFLE:
+        return "IFLE";
+      case IF_ICMPEQ:
+        return "IF_ICMPEQ";
+      case IF_ICMPNE:
+        return "IF_ICMPNE";
+      case IF_ICMPLT:
+        return "IF_ICMPLT";
+      case IF_ICMPGE:
+        return "IF_ICMPGE";
+      case IF_ICMPGT:
+        return "IF_ICMPGT";
+      case IF_ICMPLE:
+        return "IF_ICMPLE";
+      case IF_ACMPEQ:
+        return "IF_ACMPEQ";
+      case IF_ACMPNE:
+        return "IF_ACMPNE";
+      case GOTO:
+        return "GOTO";
+        // case JSR: return "JSR";
+        // case RET: return "RET";
+      case TABLESWITCH:
+        return "TABLESWITCH";
+      case LOOKUPSWITCH:
+        return "LOOKUPSWITCH";
+      case IRETURN:
+        return "IRETURN";
+      case LRETURN:
+        return "LRETURN";
+      case FRETURN:
+        return "FRETURN";
+      case DRETURN:
+        return "DRETURN";
+      case ARETURN:
+        return "ARETURN";
+      case RETURN:
+        return "RETURN";
+      case GETSTATIC:
+        return "GETSTATIC";
+      case PUTSTATIC:
+        return "PUTSTATIC";
+      case GETFIELD:
+        return "GETFIELD";
+      case PUTFIELD:
+        return "PUTFIELD";
+      case INVOKEVIRTUAL:
+        return "INVOKEVIRTUAL";
+      case INVOKESPECIAL:
+        return "INVOKESPECIAL";
+      case INVOKESTATIC:
+        return "INVOKESTATIC";
+      case INVOKEINTERFACE:
+        return "INVOKEINTERFACE";
+      case INVOKEDYNAMIC:
+        return "INVOKEDYNAMIC";
+      case NEW:
+        return "NEW";
+      case NEWARRAY:
+        return "NEWARRAY";
+      case ANEWARRAY:
+        return "ANEWARRAY";
+      case ARRAYLENGTH:
+        return "ARRAYLENGTH";
+      case ATHROW:
+        return "ATHROW";
+      case CHECKCAST:
+        return "CHECKCAST";
+      case INSTANCEOF:
+        return "INSTANCEOF";
+      case MONITORENTER:
+        return "MONITORENTER";
+      case MONITOREXIT:
+        return "MONITOREXIT";
+      case MULTIANEWARRAY:
+        return "MULTIANEWARRAY";
+      case IFNULL:
+        return "IFNULL";
+      case IFNONNULL:
+        return "IFNONNULL";
+
+        // Non-CF instructions.
+      case ICONST:
+        return "ICONST";
+      case LCONST:
+        return "LCONST";
+      case FCONST:
+        return "FCONST";
+      case DCONST:
+        return "DCONST";
+      case INVOKEDIRECT:
+        return "INVOKEDIRECT";
+      case DEBUGPOS:
+        return "DEBUGPOS";
+
+      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
new file mode 100644
index 0000000..317eeee
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/LIRParsedInstructionCallback.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.lightir;
+
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItem;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
+
+/**
+ * Structured callbacks for interpreting LIR.
+ *
+ * <p>This callback parses the actual instructions and dispatches to instruction specific methods
+ * where the parsed data is provided as arguments. Instructions that are part of a family of
+ * instructions have a default implementation that will call the "instruction family" methods (e.g.,
+ * onInvokeVirtual will default dispatch to onInvokedMethodInstruction).
+ *
+ * <p>Due to the parsing of the individual instructions, this parser has a higher overhead than
+ * using the basic {@code LIRInstructionView}.
+ */
+public class LIRParsedInstructionCallback implements LIRInstructionCallback {
+
+  private final LIRCode code;
+
+  public LIRParsedInstructionCallback(LIRCode code) {
+    this.code = code;
+  }
+
+  public void onConstNull() {}
+
+  public void onConstString(DexString string) {}
+
+  public void onInvokeMethodInstruction(DexMethod method, IntList arguments) {}
+
+  public void onInvokeDirect(DexMethod method, IntList arguments) {
+    onInvokeMethodInstruction(method, arguments);
+  }
+
+  public void onInvokeVirtual(DexMethod method, IntList arguments) {
+    onInvokeMethodInstruction(method, arguments);
+  }
+
+  public void onFieldInstruction(DexField field) {
+    onFieldInstruction(field);
+  }
+
+  public void onStaticGet(DexField field) {
+    onFieldInstruction(field);
+  }
+
+  public void onReturnVoid() {}
+
+  public void onDebugPosition() {}
+
+  private DexItem getConstantItem(int index) {
+    return code.getConstantItem(index);
+  }
+
+  @Override
+  public final void onInstructionView(LIRInstructionView view) {
+    switch (view.getOpcode()) {
+      case LIROpcodes.ACONST_NULL:
+        {
+          onConstNull();
+          break;
+        }
+      case LIROpcodes.LDC:
+        {
+          DexItem item = getConstantItem(view.getNextConstantOperand());
+          if (item instanceof DexString) {
+            onConstString((DexString) item);
+          }
+          break;
+        }
+      case LIROpcodes.INVOKEDIRECT:
+        {
+          DexMethod target = getInvokeInstructionTarget(view);
+          IntList arguments = getInvokeInstructionArguments(view);
+          onInvokeDirect(target, arguments);
+          break;
+        }
+      case LIROpcodes.INVOKEVIRTUAL:
+        {
+          DexMethod target = getInvokeInstructionTarget(view);
+          IntList arguments = getInvokeInstructionArguments(view);
+          onInvokeVirtual(target, arguments);
+          break;
+        }
+      case LIROpcodes.GETSTATIC:
+        {
+          DexField field = (DexField) getConstantItem(view.getNextConstantOperand());
+          onStaticGet(field);
+          break;
+        }
+      case LIROpcodes.RETURN:
+        {
+          onReturnVoid();
+          break;
+        }
+      case LIROpcodes.DEBUGPOS:
+        {
+          onDebugPosition();
+          break;
+        }
+      default:
+        throw new Unimplemented("No dispatch for opcode " + LIROpcodes.toString(view.getOpcode()));
+    }
+  }
+
+  private DexMethod getInvokeInstructionTarget(LIRInstructionView view) {
+    return (DexMethod) getConstantItem(view.getNextConstantOperand());
+  }
+
+  private IntList getInvokeInstructionArguments(LIRInstructionView view) {
+    IntList arguments = new IntArrayList();
+    while (view.hasMoreOperands()) {
+      arguments.add(view.getNextValueOperand());
+    }
+    return arguments;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 668f6de..922fc3d 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -28,10 +28,14 @@
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
@@ -45,8 +49,10 @@
 
   public static class Builder extends ProguardMap.Builder {
 
+    private boolean buildPreamble = false;
+    private final List<String> preamble = new ArrayList<>();
     private final Map<String, ClassNamingForNameMapper.Builder> mapping = new HashMap<>();
-    private LinkedHashSet<MapVersionMappingInformation> mapVersions = new LinkedHashSet<>();
+    private final LinkedHashSet<MapVersionMappingInformation> mapVersions = new LinkedHashSet<>();
     private final Map<String, String> originalSourceFiles = new HashMap<>();
 
     @Override
@@ -58,9 +64,22 @@
       return classNamingBuilder;
     }
 
+    Builder setBuildPreamble(boolean buildPreamble) {
+      this.buildPreamble = buildPreamble;
+      return this;
+    }
+
+    @Override
+    void addPreambleLine(String line) {
+      if (buildPreamble) {
+        preamble.add(line);
+      }
+    }
+
     @Override
     public ClassNameMapper build() {
-      return new ClassNameMapper(buildClassNameMappings(), mapVersions, originalSourceFiles);
+      return new ClassNameMapper(
+          buildClassNameMappings(), mapVersions, originalSourceFiles, preamble);
     }
 
     private ImmutableMap<String, ClassNamingForNameMapper> buildClassNameMappings() {
@@ -100,6 +119,11 @@
     return mapperFromBufferedReader(CharSource.wrap(contents).openBufferedStream(), null);
   }
 
+  public static ClassNameMapper mapperFromStringWithPreamble(String contents) throws IOException {
+    return mapperFromBufferedReader(
+        CharSource.wrap(contents).openBufferedStream(), null, false, false, true);
+  }
+
   public static ClassNameMapper mapperFromString(
       String contents, DiagnosticsHandler diagnosticsHandler) throws IOException {
     return mapperFromBufferedReader(
@@ -110,38 +134,43 @@
       String contents,
       DiagnosticsHandler diagnosticsHandler,
       boolean allowEmptyMappedRanges,
-      boolean allowExperimentalMapping)
+      boolean allowExperimentalMapping,
+      boolean readPreamble)
       throws IOException {
     return mapperFromLineReader(
         LineReader.fromBufferedReader(CharSource.wrap(contents).openBufferedStream()),
         diagnosticsHandler,
         allowEmptyMappedRanges,
-        allowExperimentalMapping);
+        allowExperimentalMapping,
+        readPreamble);
   }
 
   private static ClassNameMapper mapperFromBufferedReader(
       BufferedReader reader, DiagnosticsHandler diagnosticsHandler) throws IOException {
-    return mapperFromBufferedReader(reader, diagnosticsHandler, false, false);
+    return mapperFromBufferedReader(reader, diagnosticsHandler, false, false, false);
   }
 
   public static ClassNameMapper mapperFromBufferedReader(
       BufferedReader reader,
       DiagnosticsHandler diagnosticsHandler,
       boolean allowEmptyMappedRanges,
-      boolean allowExperimentalMapping)
+      boolean allowExperimentalMapping,
+      boolean buildPreamble)
       throws IOException {
     return mapperFromLineReader(
         LineReader.fromBufferedReader(reader),
         diagnosticsHandler,
         allowEmptyMappedRanges,
-        allowExperimentalMapping);
+        allowExperimentalMapping,
+        buildPreamble);
   }
 
   public static ClassNameMapper mapperFromLineReader(
       LineReader reader,
       DiagnosticsHandler diagnosticsHandler,
       boolean allowEmptyMappedRanges,
-      boolean allowExperimentalMapping)
+      boolean allowExperimentalMapping,
+      boolean buildPreamble)
       throws IOException {
     try (ProguardMapReader proguardReader =
         new ProguardMapReader(
@@ -149,7 +178,7 @@
             diagnosticsHandler != null ? diagnosticsHandler : new Reporter(),
             allowEmptyMappedRanges,
             allowExperimentalMapping)) {
-      ClassNameMapper.Builder builder = ClassNameMapper.builder();
+      ClassNameMapper.Builder builder = ClassNameMapper.builder().setBuildPreamble(buildPreamble);
       proguardReader.parse(builder);
       return builder.build();
     }
@@ -180,20 +209,27 @@
   private final Map<Signature, Signature> signatureMap = new HashMap<>();
   private final LinkedHashSet<MapVersionMappingInformation> mapVersions;
   private final Map<String, String> originalSourceFiles;
+  private final List<String> preamble;
 
   private ClassNameMapper(
       ImmutableMap<String, ClassNamingForNameMapper> classNameMappings,
       LinkedHashSet<MapVersionMappingInformation> mapVersions,
-      Map<String, String> originalSourceFiles) {
+      Map<String, String> originalSourceFiles,
+      List<String> preamble) {
     this.classNameMappings = classNameMappings;
     this.mapVersions = mapVersions;
     this.originalSourceFiles = originalSourceFiles;
+    this.preamble = preamble;
   }
 
   public Map<String, ClassNamingForNameMapper> getClassNameMappings() {
     return classNameMappings;
   }
 
+  public Collection<String> getPreamble() {
+    return preamble;
+  }
+
   private Signature canonicalizeSignature(Signature signature) {
     Signature result = signatureMap.get(signature);
     if (result != null) {
@@ -274,7 +310,14 @@
     // This will overwrite existing source files but the chance of that happening should be very
     // slim.
     newSourcesFiles.putAll(other.originalSourceFiles);
-    return new ClassNameMapper(builder.build(), newMapVersions, newSourcesFiles);
+
+    List<String> newPreamble = Collections.emptyList();
+    if (!this.preamble.isEmpty() || !other.preamble.isEmpty()) {
+      newPreamble = new ArrayList<>();
+      newPreamble.addAll(this.preamble);
+      newPreamble.addAll(other.preamble);
+    }
+    return new ClassNameMapper(builder.build(), newMapVersions, newSourcesFiles, newPreamble);
   }
 
   @Override
@@ -301,7 +344,7 @@
     ImmutableMap.Builder<String, ClassNamingForNameMapper> builder = ImmutableMap.builder();
     builder.orderEntriesByValue(Comparator.comparing(x -> x.originalName));
     classNameMappings.forEach(builder::put);
-    return new ClassNameMapper(builder.build(), mapVersions, originalSourceFiles);
+    return new ClassNameMapper(builder.build(), mapVersions, originalSourceFiles, preamble);
   }
 
   public boolean verifyIsSorted() {
diff --git a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
index 6e1bc00..3919bbb 100644
--- a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
+++ b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
@@ -11,14 +11,22 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
+import com.android.tools.r8.naming.mappinginformation.OutlineCallsiteMappingInformation;
+import com.android.tools.r8.naming.mappinginformation.OutlineMappingInformation;
 import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation;
 import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation.ThrowsCondition;
+import com.android.tools.r8.references.ArrayReference;
+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.BiMapContainer;
+import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.utils.ChainableStringConsumer;
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.SegmentTree;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2IntSortedMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import java.util.ArrayList;
@@ -28,48 +36,70 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
 
 public class ComposingBuilder {
 
-  /**
-   * To ensure we can do alpha renaming of classes and members without polluting the existing
-   * mappping, we use a committed map that we update for each class name mapping. That allows us to
-   * rename to existing renamed names as long as these are also renamed later in the map.
-   */
-  private final Map<String, ComposingClassBuilder> committed = new HashMap<>();
-
-  private Map<String, ComposingClassBuilder> current = new HashMap<>();
-
   private MapVersionMappingInformation currentMapVersion = null;
 
-  private final ComposingSharedData sharedData = new ComposingSharedData();
+  /**
+   * When composing we store a view of the previously known mappings in committed and retain a
+   * current working set. When composing of a new map is finished we commit everything in current
+   * into the committed set.
+   *
+   * <p>The reason for not having just a single set is that we can have a circular mapping as
+   * follows:
+   *
+   * <pre>
+   *   a -> b:
+   *   ...
+   *   b -> a:
+   * </pre>
+   *
+   * After composing our current view of a with the above, we could end up transforming 'a' into 'b'
+   * and then later transforming 'b' back into 'a' again. To ensure we do not mess up namings while
+   * composing classes and methods we resort to a working set and committed set.
+   */
+  private final ComposingData committed = new ComposingData();
+
+  private ComposingData current;
 
   public void compose(ClassNameMapper classNameMapper) throws MappingComposeException {
-    MapVersionMappingInformation thisMapVersion = classNameMapper.getFirstMapVersionInformation();
-    if (thisMapVersion != null) {
+    current = new ComposingData();
+    MapVersionMappingInformation newMapVersionInfo =
+        classNameMapper.getFirstMapVersionInformation();
+    if (newMapVersionInfo != null) {
+      MapVersion newMapVersion = newMapVersionInfo.getMapVersion();
+      if (newMapVersion.isLessThan(MapVersion.MAP_VERSION_2_1) || newMapVersion.isUnknown()) {
+        throw new MappingComposeException(
+            "Composition of mapping files supported from map version 2.1.");
+      }
       if (currentMapVersion == null
-          || currentMapVersion.getMapVersion().isLessThan(thisMapVersion.getMapVersion())) {
-        currentMapVersion = thisMapVersion;
+          || currentMapVersion.getMapVersion().isLessThan(newMapVersion)) {
+        currentMapVersion = newMapVersionInfo;
       }
     }
-    sharedData.patchupMappingInformation(classNameMapper);
     for (ClassNamingForNameMapper classMapping : classNameMapper.getClassNameMappings().values()) {
       compose(classMapping);
     }
-    commit();
+    committed.commit(current, classNameMapper);
   }
 
   private void compose(ClassNamingForNameMapper classMapping) throws MappingComposeException {
     String originalName = classMapping.originalName;
-    ComposingClassBuilder composingClassBuilder = committed.get(originalName);
+    ComposingClassBuilder composingClassBuilder = committed.classBuilders.get(originalName);
     String renamedName = classMapping.renamedName;
     if (composingClassBuilder == null) {
-      composingClassBuilder = new ComposingClassBuilder(originalName, renamedName, sharedData);
+      composingClassBuilder = new ComposingClassBuilder(originalName, renamedName);
     } else {
       composingClassBuilder.setRenamedName(renamedName);
-      committed.remove(originalName);
+      committed.classBuilders.remove(originalName);
     }
-    ComposingClassBuilder duplicateMapping = current.put(renamedName, composingClassBuilder);
+    composingClassBuilder.setCurrentComposingData(current, classMapping.originalName);
+    ComposingClassBuilder duplicateMapping =
+        current.classBuilders.put(renamedName, composingClassBuilder);
     if (duplicateMapping != null) {
       throw new MappingComposeException(
           "Duplicate class mapping. Both '"
@@ -83,31 +113,13 @@
     composingClassBuilder.compose(classMapping);
   }
 
-  private void commit() throws MappingComposeException {
-    for (Entry<String, ComposingClassBuilder> newEntry : current.entrySet()) {
-      String renamedName = newEntry.getKey();
-      ComposingClassBuilder classBuilder = newEntry.getValue();
-      ComposingClassBuilder duplicateMapping = committed.put(renamedName, classBuilder);
-      if (duplicateMapping != null) {
-        throw new MappingComposeException(
-            "Duplicate class mapping. Both '"
-                + duplicateMapping.getOriginalName()
-                + "' and '"
-                + classBuilder.getOriginalName()
-                + "' maps to '"
-                + renamedName
-                + "'.");
-      }
-    }
-    current = new HashMap<>();
-  }
 
   @Override
   public String toString() {
-    List<ComposingClassBuilder> classBuilders = new ArrayList<>(committed.values());
+    List<ComposingClassBuilder> classBuilders = new ArrayList<>(committed.classBuilders.values());
     classBuilders.sort(Comparator.comparing(ComposingClassBuilder::getOriginalName));
     StringBuilder sb = new StringBuilder();
-    // TODO(b/241763080): Keep preamble of mapping files"
+    committed.preamble.forEach(preambleLine -> sb.append(preambleLine).append("\n"));
     if (currentMapVersion != null) {
       sb.append("# ").append(currentMapVersion.serialize()).append("\n");
     }
@@ -118,34 +130,246 @@
     return sb.toString();
   }
 
-  public static class ComposingSharedData {
+  public static class ComposingData {
 
     /**
+     * A map of minified names to their class builders. When committing to a new minified name we
+     * destructively remove the previous minified mapping and replace it with the up-to-date one.
+     */
+    private final Map<String, ComposingClassBuilder> classBuilders = new HashMap<>();
+    /**
      * RewriteFrameInformation contains condition clauses that are bound to the residual program. As
      * a result of that, we have to patch up the conditions when we compose new class mappings.
      */
-    private final List<RewriteFrameMappingInformation> mappingInformationToPatchUp =
-        new ArrayList<>();
+    private final List<RewriteFrameMappingInformation> rewriteFrameInformation = new ArrayList<>();
+    /** Map of newly added outline call site informations which do not require any rewriting. */
+    private Map<ClassDescriptorAndMethodName, OutlineCallsiteMappingInformation>
+        outlineCallsiteInformation = new HashMap<>();
+    /**
+     * Map of updated outline definitions which has to be committed. The positions in the caller are
+     * fixed at this point since these are local to the method when rewriting.
+     */
+    private final Map<ClassDescriptorAndMethodName, UpdateOutlineCallsiteInformation>
+        outlineSourcePositionsUpdated = new HashMap<>();
 
-    private void patchupMappingInformation(ClassNameMapper classNameMapper) {
-      BiMapContainer<String, String> obfuscatedToOriginalMapping =
-          classNameMapper.getObfuscatedToOriginalMapping();
-      for (RewriteFrameMappingInformation rewriteMappingInfo : mappingInformationToPatchUp) {
+    private final List<String> preamble = new ArrayList<>();
+
+    public void commit(ComposingData current, ClassNameMapper classNameMapper)
+        throws MappingComposeException {
+      preamble.addAll(classNameMapper.getPreamble());
+      commitClassBuilders(current);
+      commitRewriteFrameInformation(current, classNameMapper);
+      commitOutlineCallsiteInformation(current, classNameMapper);
+    }
+
+    private void commitClassBuilders(ComposingData current) throws MappingComposeException {
+      for (Entry<String, ComposingClassBuilder> newEntry : current.classBuilders.entrySet()) {
+        String renamedName = newEntry.getKey();
+        ComposingClassBuilder classBuilder = newEntry.getValue();
+        ComposingClassBuilder duplicateMapping = classBuilders.put(renamedName, classBuilder);
+        if (duplicateMapping != null) {
+          throw new MappingComposeException(
+              "Duplicate class mapping. Both '"
+                  + duplicateMapping.getOriginalName()
+                  + "' and '"
+                  + classBuilder.getOriginalName()
+                  + "' maps to '"
+                  + renamedName
+                  + "'.");
+        }
+      }
+    }
+
+    private void commitRewriteFrameInformation(
+        ComposingData current, ClassNameMapper classNameMapper) {
+      // First update the existing frame information to have new class name mappings.
+      Map<String, String> inverse = classNameMapper.getObfuscatedToOriginalMapping().inverse;
+      for (RewriteFrameMappingInformation rewriteMappingInfo : rewriteFrameInformation) {
         rewriteMappingInfo
             .getConditions()
             .forEach(
                 rewriteCondition -> {
                   ThrowsCondition throwsCondition = rewriteCondition.asThrowsCondition();
                   if (throwsCondition != null) {
-                    String originalName = throwsCondition.getClassReference().getTypeName();
-                    String obfuscatedName = obfuscatedToOriginalMapping.inverse.get(originalName);
-                    if (obfuscatedName != null) {
-                      throwsCondition.setClassReferenceInternal(
-                          Reference.classFromTypeName(obfuscatedName));
-                    }
+                    throwsCondition.setClassReferenceInternal(
+                        mapTypeReference(inverse, throwsCondition.getClassReference()).asClass());
                   }
                 });
       }
+      rewriteFrameInformation.addAll(current.rewriteFrameInformation);
+    }
+
+    private void commitOutlineCallsiteInformation(
+        ComposingData current, ClassNameMapper classNameMapper) {
+      // To commit outline call site information, we take the previously committed and bring forward
+      // to a new mapping, and potentially rewrite source positions if available.
+      Map<ClassDescriptorAndMethodName, OutlineCallsiteMappingInformation> newOutlineCallsiteInfo =
+          new HashMap<>();
+      Map<String, String> inverse = classNameMapper.getObfuscatedToOriginalMapping().inverse;
+      outlineCallsiteInformation.forEach(
+          (holderAndMethodNameOfOutline, outlineInfo) -> {
+            UpdateOutlineCallsiteInformation updateOutlineCallsiteInformation =
+                current.outlineSourcePositionsUpdated.get(holderAndMethodNameOfOutline);
+            String newMethodName = outlineInfo.getOutline().getMethodName();
+            if (updateOutlineCallsiteInformation != null) {
+              // We have a callsite mapping that we need to update.
+              MappedRangeOriginalToMinifiedMap originalToMinifiedMap =
+                  MappedRangeOriginalToMinifiedMap.build(
+                      updateOutlineCallsiteInformation.newMappedRanges);
+              Int2IntSortedMap newPositionMap = new Int2IntLinkedOpenHashMap();
+              outlineInfo
+                  .getPositions()
+                  .forEach(
+                      (originalPosition, destination) -> {
+                        originalToMinifiedMap.visitMinified(
+                            originalPosition,
+                            newMinified -> {
+                              newPositionMap.put(newMinified, destination);
+                            });
+                      });
+              outlineInfo.setPositionsInternal(newPositionMap);
+              newMethodName = updateOutlineCallsiteInformation.newMethodName;
+            }
+            // Holder, return type or formals could have changed the outline descriptor.
+            MethodReference outline = outlineInfo.getOutline();
+            ClassReference newHolder =
+                mapTypeReference(inverse, outline.getHolderClass()).asClass();
+            outlineInfo.setOutlineInternal(
+                Reference.method(
+                    newHolder,
+                    newMethodName,
+                    mapTypeReferences(inverse, outline.getFormalTypes()),
+                    mapTypeReference(inverse, outline.getReturnType())));
+            newOutlineCallsiteInfo.put(
+                new ClassDescriptorAndMethodName(
+                    newHolder.getTypeName(), holderAndMethodNameOfOutline.getMethodName()),
+                outlineInfo);
+          });
+      newOutlineCallsiteInfo.putAll(current.outlineCallsiteInformation);
+      outlineCallsiteInformation = newOutlineCallsiteInfo;
+    }
+
+    public void addNewOutlineCallsiteInformation(
+        MethodReference outline, OutlineCallsiteMappingInformation outlineCallsiteInfo) {
+      outlineCallsiteInformation.put(
+          new ClassDescriptorAndMethodName(
+              outline.getHolderClass().getTypeName(), outline.getMethodName()),
+          outlineCallsiteInfo);
+    }
+
+    public UpdateOutlineCallsiteInformation getUpdateOutlineCallsiteInformation(
+        String originalHolder, String originalMethodName, String newMethodName) {
+      return outlineSourcePositionsUpdated.computeIfAbsent(
+          new ClassDescriptorAndMethodName(originalHolder, originalMethodName),
+          ignore -> new UpdateOutlineCallsiteInformation(newMethodName));
+    }
+
+    private List<TypeReference> mapTypeReferences(
+        Map<String, String> typeNameMap, List<TypeReference> typeReferences) {
+      return ListUtils.map(typeReferences, typeRef -> mapTypeReference(typeNameMap, typeRef));
+    }
+
+    private TypeReference mapTypeReference(
+        Map<String, String> typeNameMap, TypeReference typeReference) {
+      if (typeReference == null || typeReference.isPrimitive()) {
+        return typeReference;
+      }
+      if (typeReference.isArray()) {
+        ArrayReference arrayReference = typeReference.asArray();
+        return Reference.array(
+            mapTypeReference(typeNameMap, arrayReference.getBaseType()),
+            arrayReference.getDimensions());
+      } else {
+        assert typeReference.isClass();
+        String newTypeName = typeNameMap.get(typeReference.getTypeName());
+        return newTypeName == null ? typeReference : Reference.classFromTypeName(newTypeName);
+      }
+    }
+  }
+
+  private static class ClassDescriptorAndMethodName {
+
+    private final String holderTypeName;
+    private final String methodName;
+
+    public ClassDescriptorAndMethodName(String holderTypeName, String methodName) {
+      this.holderTypeName = holderTypeName;
+      this.methodName = methodName;
+    }
+
+    public String getHolderTypeName() {
+      return holderTypeName;
+    }
+
+    public String getMethodName() {
+      return methodName;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (!(o instanceof ClassDescriptorAndMethodName)) {
+        return false;
+      }
+      ClassDescriptorAndMethodName that = (ClassDescriptorAndMethodName) o;
+      return holderTypeName.equals(that.holderTypeName) && methodName.equals(that.methodName);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(holderTypeName, methodName);
+    }
+  }
+
+  private static class UpdateOutlineCallsiteInformation {
+
+    private List<MappedRange> newMappedRanges;
+    private final String newMethodName;
+
+    private UpdateOutlineCallsiteInformation(String newMethodName) {
+      this.newMethodName = newMethodName;
+    }
+
+    private void setNewMappedRanges(List<MappedRange> mappedRanges) {
+      newMappedRanges = mappedRanges;
+    }
+  }
+
+  private static class MappedRangeOriginalToMinifiedMap {
+
+    private final Int2ReferenceMap<List<Integer>> originalToMinified;
+
+    private MappedRangeOriginalToMinifiedMap(Int2ReferenceMap<List<Integer>> originalToMinified) {
+      this.originalToMinified = originalToMinified;
+    }
+
+    private static MappedRangeOriginalToMinifiedMap build(List<MappedRange> mappedRanges) {
+      Int2ReferenceMap<List<Integer>> positionMap = new Int2ReferenceOpenHashMap<>();
+      for (MappedRange mappedRange : mappedRanges) {
+        Range originalRange = mappedRange.originalRange;
+        for (int position = originalRange.from; position <= originalRange.to; position++) {
+          // It is perfectly fine to have multiple minified ranges mapping to the same source, we
+          // just need to keep the additional information.
+          positionMap
+              .computeIfAbsent(position, ignoreArgument(ArrayList::new))
+              .add(mappedRange.minifiedRange.from + (position - originalRange.from));
+        }
+      }
+      return new MappedRangeOriginalToMinifiedMap(positionMap);
+    }
+
+    public int lookupFirst(int originalPosition) {
+      List<Integer> minifiedPositions = originalToMinified.get(originalPosition);
+      return minifiedPositions == null ? 0 : minifiedPositions.get(0);
+    }
+
+    public void visitMinified(int originalPosition, Consumer<Integer> consumer) {
+      List<Integer> minifiedPositions = originalToMinified.get(originalPosition);
+      if (minifiedPositions != null) {
+        minifiedPositions.forEach(consumer);
+      }
     }
   }
 
@@ -163,13 +387,23 @@
     // one method since any shrinker should put in line numbers for overloads.
     private final Map<String, SegmentTree<List<MappedRange>>> methodMembers = new HashMap<>();
     private List<MappingInformation> additionalMappingInfo = null;
-    private final ComposingSharedData sharedData;
 
-    private ComposingClassBuilder(
-        String originalName, String renamedName, ComposingSharedData sharedData) {
+    private ComposingData current;
+
+    /**
+     * Keeps track of the current original name which is different from originalName if this is a
+     * subsequent mapping.
+     */
+    private String currentOriginalName;
+
+    private ComposingClassBuilder(String originalName, String renamedName) {
       this.originalName = originalName;
       this.renamedName = renamedName;
-      this.sharedData = sharedData;
+    }
+
+    public void setCurrentComposingData(ComposingData current, String currentMinifiedName) {
+      this.current = current;
+      this.currentOriginalName = currentMinifiedName;
     }
 
     public void setRenamedName(String renamedName) {
@@ -268,7 +502,8 @@
      * long as the current mapped range is the same method and return a mapped range result
      * containing all ranges for a method along with some additional information.
      */
-    private MappedRangeResult getMappedRangesForMethod(List<MappedRange> mappedRanges, int index) {
+    private MappedRangeResult getMappedRangesForMethod(List<MappedRange> mappedRanges, int index)
+        throws MappingComposeException {
       if (index >= mappedRanges.size()) {
         return null;
       }
@@ -297,10 +532,30 @@
             && !isInlineMappedRange(mappedRanges, i)) {
           break;
         }
+        // Register mapping information that is dependent on the residual naming to allow updating
+        // later on.
         for (MappingInformation mappingInformation : thisMappedRange.getAdditionalMappingInfo()) {
           if (mappingInformation.isRewriteFrameMappingInformation()) {
-            sharedData.mappingInformationToPatchUp.add(
-                mappingInformation.asRewriteFrameMappingInformation());
+            RewriteFrameMappingInformation rewriteFrameMappingInformation =
+                mappingInformation.asRewriteFrameMappingInformation();
+            rewriteFrameMappingInformation
+                .getConditions()
+                .forEach(
+                    condition -> {
+                      if (condition.isThrowsCondition()) {
+                        current.rewriteFrameInformation.add(rewriteFrameMappingInformation);
+                      }
+                    });
+          } else if (mappingInformation.isOutlineCallsiteInformation()) {
+            OutlineCallsiteMappingInformation outlineCallsiteInfo =
+                mappingInformation.asOutlineCallsiteInformation();
+            MethodReference outline = outlineCallsiteInfo.getOutline();
+            if (outline == null) {
+              throw new MappingComposeException(
+                  "Unable to compose outline call site information without outline key: "
+                      + outlineCallsiteInfo.serialize());
+            }
+            current.addNewOutlineCallsiteInformation(outline, outlineCallsiteInfo);
           }
         }
         seenMappedRanges.add(thisMappedRange);
@@ -345,6 +600,7 @@
       Int2ReferenceMap<List<MappedRange>> mappedRangesForPosition =
           getExistingMapping(existingRanges);
       List<MappedRange> newComposedRanges = new ArrayList<>();
+      ComputedOutlineInformation computedOutlineInformation = new ComputedOutlineInformation();
       for (int i = 0; i < newRanges.size(); i++) {
         if (isInlineMappedRange(newRanges, i)) {
           throw new MappingComposeException(
@@ -378,6 +634,7 @@
                 newComposedRanges,
                 newRange,
                 existingMappedRanges,
+                computedOutlineInformation,
                 newRange.minifiedRange.from,
                 newRange.minifiedRange.to);
           } else {
@@ -403,6 +660,7 @@
                     newComposedRanges,
                     newRange,
                     existingMappedRanges,
+                    computedOutlineInformation,
                     lastStartingMinifiedFrom,
                     position - 1);
                 lastStartingMinifiedFrom = position;
@@ -413,11 +671,41 @@
                 newComposedRanges,
                 newRange,
                 existingMappedRanges,
+                computedOutlineInformation,
                 lastStartingMinifiedFrom,
                 newRange.minifiedRange.to);
           }
         }
       }
+      MappedRange lastComposedRange = ListUtils.last(newComposedRanges);
+      if (computedOutlineInformation.seenOutlineMappingInformation != null) {
+        current
+            .getUpdateOutlineCallsiteInformation(
+                currentOriginalName,
+                ListUtils.last(newRanges).signature.getName(),
+                lastComposedRange.renamedName)
+            .setNewMappedRanges(newRanges);
+        lastComposedRange.addMappingInformation(
+            computedOutlineInformation.seenOutlineMappingInformation,
+            ConsumerUtils.emptyConsumer());
+      }
+      if (!computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.isEmpty()) {
+        MappedRangeOriginalToMinifiedMap originalToMinifiedMap =
+            MappedRangeOriginalToMinifiedMap.build(newRanges);
+        List<OutlineCallsiteMappingInformation> outlineCallSites =
+            new ArrayList<>(computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp);
+        outlineCallSites.sort(Comparator.comparing(mapping -> mapping.getOutline().toString()));
+        for (OutlineCallsiteMappingInformation outlineCallSite : outlineCallSites) {
+          Int2IntSortedMap positionMap = outlineCallSite.getPositions();
+          for (Integer keyPosition : positionMap.keySet()) {
+            int keyPositionInt = keyPosition;
+            int originalDestination = positionMap.get(keyPositionInt);
+            int newDestination = originalToMinifiedMap.lookupFirst(originalDestination);
+            positionMap.put(keyPositionInt, newDestination);
+          }
+          lastComposedRange.addMappingInformation(outlineCallSite, ConsumerUtils.emptyConsumer());
+        }
+      }
       return newComposedRanges;
     }
 
@@ -453,6 +741,7 @@
         List<MappedRange> newComposedRanges,
         MappedRange newMappedRange,
         List<MappedRange> existingMappedRanges,
+        ComputedOutlineInformation computedOutlineInformation,
         int lastStartingMinifiedFrom,
         int position) {
       Range existingRange = existingMappedRanges.get(0).minifiedRange;
@@ -485,7 +774,17 @@
         existingMappedRange
             .getAdditionalMappingInfo()
             .forEach(
-                info -> computedRange.addMappingInformation(info, ConsumerUtils.emptyConsumer()));
+                info -> {
+                  if (info.isOutlineMappingInformation()) {
+                    computedOutlineInformation.seenOutlineMappingInformation =
+                        info.asOutlineMappingInformation();
+                  } else if (info.isOutlineCallsiteInformation()) {
+                    computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.add(
+                        info.asOutlineCallsiteInformation());
+                  } else {
+                    computedRange.addMappingInformation(info, ConsumerUtils.emptyConsumer());
+                  }
+                });
         newComposedRanges.add(computedRange);
       }
     }
@@ -573,5 +872,11 @@
         this.allRanges = allRanges;
       }
     }
+
+    private static class ComputedOutlineInformation {
+      private final Set<OutlineCallsiteMappingInformation>
+          outlineCallsiteMappingInformationToPatchUp = Sets.newIdentityHashSet();
+      private OutlineMappingInformation seenOutlineMappingInformation = null;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/MapVersion.java b/src/main/java/com/android/tools/r8/naming/MapVersion.java
index 8d8c9e0..322d15d 100644
--- a/src/main/java/com/android/tools/r8/naming/MapVersion.java
+++ b/src/main/java/com/android/tools/r8/naming/MapVersion.java
@@ -12,10 +12,11 @@
   MAP_VERSION_NONE("none"),
   MAP_VERSION_1_0("1.0"),
   MAP_VERSION_2_0("2.0"),
+  MAP_VERSION_2_1("2.1"),
   MAP_VERSION_EXPERIMENTAL("experimental"),
   MAP_VERSION_UNKNOWN("unknown");
 
-  public static final MapVersion STABLE = MAP_VERSION_2_0;
+  public static final MapVersion STABLE = MAP_VERSION_2_1;
 
   private final String name;
 
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMap.java b/src/main/java/com/android/tools/r8/naming/ProguardMap.java
index 6b95444..38fe729 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMap.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMap.java
@@ -13,6 +13,8 @@
     abstract ClassNaming.Builder classNamingBuilder(
         String renamedName, String originalName, Position position);
 
+    abstract void addPreambleLine(String line);
+
     abstract Builder setCurrentMapVersion(MapVersionMappingInformation mapVersion);
 
     abstract ProguardMap build();
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index a3a3aa0..edb8793 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -69,6 +69,7 @@
   private final DiagnosticsHandler diagnosticsHandler;
   private final boolean allowEmptyMappedRanges;
   private final boolean allowExperimentalMapping;
+  private boolean seenClassMapping = false;
 
   private final CardinalPositionRangeAllocator cardinalRangeCache =
       PositionRangeAllocator.createCardinalPositionRangeAllocator();
@@ -147,11 +148,11 @@
     }
   }
 
-  private boolean nextLine() throws IOException {
+  private boolean nextLine(ProguardMap.Builder mapBuilder) throws IOException {
     if (line.length() != lineOffset) {
       throw new ParseException("Expected end of line");
     }
-    return skipLine();
+    return skipLine(mapBuilder);
   }
 
   private boolean isEmptyOrCommentLine(String line) {
@@ -184,10 +185,6 @@
     return false;
   }
 
-  private boolean isClassMapping() {
-    return !isEmptyOrCommentLine(line) && line.endsWith(":");
-  }
-
   private static boolean hasFirstCharJsonBrace(String line, int commentCharIndex) {
     for (int i = commentCharIndex + 1; i < line.length(); i++) {
       char c = line.charAt(i);
@@ -200,12 +197,17 @@
     return false;
   }
 
-  private boolean skipLine() throws IOException {
+  private boolean skipLine(ProguardMap.Builder mapBuilder) throws IOException {
     lineOffset = 0;
+    boolean isEmptyOrCommentLine;
     do {
-      lineNo++;
       line = reader.readLine();
-    } while (hasLine() && isEmptyOrCommentLine(line));
+      lineNo++;
+      isEmptyOrCommentLine = isEmptyOrCommentLine(line);
+      if (!seenClassMapping && isEmptyOrCommentLine) {
+        mapBuilder.addPreambleLine(line);
+      }
+    } while (hasLine() && isEmptyOrCommentLine);
     return hasLine();
   }
 
@@ -242,10 +244,7 @@
 
   void parse(ProguardMap.Builder mapBuilder) throws IOException {
     // Read the first line.
-    do {
-      line = reader.readLine();
-      lineNo++;
-    } while (hasLine() && isEmptyOrCommentLine(line));
+    skipLine(mapBuilder);
     parseClassMappings(mapBuilder);
   }
 
@@ -255,17 +254,23 @@
     while (hasLine()) {
       skipWhitespace();
       if (isCommentLineWithJsonBrace()) {
-        parseMappingInformation(
+        if (!parseMappingInformation(
             info -> {
               assert info.isMapVersionMappingInformation()
                   || info.isUnknownJsonMappingInformation();
               if (info.isMapVersionMappingInformation()) {
                 mapBuilder.setCurrentMapVersion(info.asMapVersionMappingInformation());
+              } else if (!seenClassMapping) {
+                mapBuilder.addPreambleLine(line);
               }
-            });
+            })) {
+          if (!seenClassMapping) {
+            mapBuilder.addPreambleLine(line);
+          }
+        }
         // Skip reading the rest of the line.
         lineOffset = line.length();
-        nextLine();
+        nextLine(mapBuilder);
         continue;
       }
       String before = parseType(false);
@@ -284,40 +289,47 @@
       String after = parseType(false);
       skipWhitespace();
       expect(':');
+      seenClassMapping = true;
       ClassNaming.Builder currentClassBuilder =
           mapBuilder.classNamingBuilder(after, before, getPosition());
       skipWhitespace();
-      if (nextLine()) {
-        parseMemberMappings(currentClassBuilder);
+      if (nextLine(mapBuilder)) {
+        parseMemberMappings(mapBuilder, currentClassBuilder);
       }
     }
   }
 
-  private void parseMappingInformation(Consumer<MappingInformation> onMappingInfo) {
-    MappingInformation.fromJsonObject(
-        version,
-        parseJsonInComment(),
-        diagnosticsHandler,
-        lineNo,
-        info -> {
-          MapVersionMappingInformation generatorInfo = info.asMapVersionMappingInformation();
-          if (generatorInfo != null) {
-            if (generatorInfo.getMapVersion().equals(MapVersion.MAP_VERSION_EXPERIMENTAL)) {
-              // A mapping file that is marked "experimental" will be treated as an unversioned
-              // file if the compiler/tool is not explicitly running with experimental support.
-              version =
-                  allowExperimentalMapping
-                      ? MapVersion.MAP_VERSION_EXPERIMENTAL
-                      : MapVersion.MAP_VERSION_NONE;
-            } else {
-              version = generatorInfo.getMapVersion();
+  private boolean parseMappingInformation(Consumer<MappingInformation> onMappingInfo) {
+    JsonObject object = parseJsonInComment();
+    if (object != null) {
+      MappingInformation.fromJsonObject(
+          version,
+          object,
+          diagnosticsHandler,
+          lineNo,
+          info -> {
+            MapVersionMappingInformation generatorInfo = info.asMapVersionMappingInformation();
+            if (generatorInfo != null) {
+              if (generatorInfo.getMapVersion().equals(MapVersion.MAP_VERSION_EXPERIMENTAL)) {
+                // A mapping file that is marked "experimental" will be treated as an unversioned
+                // file if the compiler/tool is not explicitly running with experimental support.
+                version =
+                    allowExperimentalMapping
+                        ? MapVersion.MAP_VERSION_EXPERIMENTAL
+                        : MapVersion.MAP_VERSION_NONE;
+              } else {
+                version = generatorInfo.getMapVersion();
+              }
             }
-          }
-          onMappingInfo.accept(info);
-        });
+            onMappingInfo.accept(info);
+          });
+      return true;
+    }
+    return false;
   }
 
-  private void parseMemberMappings(ClassNaming.Builder classNamingBuilder) throws IOException {
+  private void parseMemberMappings(
+      ProguardMap.Builder mapBuilder, ClassNaming.Builder classNamingBuilder) throws IOException {
     MemberNaming lastAddedNaming = null;
     MemberNaming activeMemberNaming = null;
     MappedRange activeMappedRange = null;
@@ -421,7 +433,7 @@
       activeMemberNaming =
           new MemberNaming(signature, signature.asRenamed(renamedName), getPosition());
       previousMappedRange = mappedRange;
-    } while (nextLine());
+    } while (nextLine(mapBuilder));
 
     if (activeMemberNaming != null) {
       boolean notAdded =
diff --git a/src/main/java/com/android/tools/r8/naming/SeedMapper.java b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
index bf8044e..45f7a5b 100644
--- a/src/main/java/com/android/tools/r8/naming/SeedMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
@@ -63,6 +63,11 @@
     }
 
     @Override
+    void addPreambleLine(String line) {
+      // Do nothing.
+    }
+
+    @Override
     ProguardMap.Builder setCurrentMapVersion(MapVersionMappingInformation mapVersion) {
       // Do nothing
       return this;
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
index 787a0bc..c7d53f9 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
@@ -66,6 +66,10 @@
     return null;
   }
 
+  public OutlineMappingInformation asOutlineMappingInformation() {
+    return null;
+  }
+
   public OutlineCallsiteMappingInformation asOutlineCallsiteInformation() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineCallsiteMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineCallsiteMappingInformation.java
index 6cb093d..efdb788 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineCallsiteMappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineCallsiteMappingInformation.java
@@ -6,6 +6,9 @@
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.naming.MapVersion;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonPrimitive;
 import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
@@ -15,14 +18,18 @@
 public class OutlineCallsiteMappingInformation extends MappingInformation {
 
   public static final MapVersion SUPPORTED_VERSION = MapVersion.MAP_VERSION_2_0;
+  public static final MapVersion SUPPORTED_WITH_OUTLINE_VERSION = MapVersion.MAP_VERSION_2_1;
   public static final String ID = "com.android.tools.r8.outlineCallsite";
 
   private static final String POSITIONS_KEY = "positions";
+  private static final String OUTLINE_KEY = "outline";
 
-  private final Int2IntSortedMap positions;
+  private Int2IntSortedMap positions;
+  private MethodReference outline;
 
-  private OutlineCallsiteMappingInformation(Int2IntSortedMap positions) {
+  private OutlineCallsiteMappingInformation(Int2IntSortedMap positions, MethodReference outline) {
     this.positions = positions;
+    this.outline = outline;
   }
 
   @Override
@@ -40,6 +47,9 @@
           mappedPositions.add(obfuscatedPosition + "", new JsonPrimitive(originalPosition));
         });
     result.add(POSITIONS_KEY, mappedPositions);
+    if (outline != null) {
+      result.add(OUTLINE_KEY, new JsonPrimitive(outline.toString()));
+    }
     return result.toString();
   }
 
@@ -62,8 +72,13 @@
     return positions.getOrDefault(originalPosition, originalPosition);
   }
 
-  public static OutlineCallsiteMappingInformation create(Int2IntSortedMap positions) {
-    return new OutlineCallsiteMappingInformation(positions);
+  public MethodReference getOutline() {
+    return outline;
+  }
+
+  public static OutlineCallsiteMappingInformation create(
+      Int2IntSortedMap positions, MethodReference outline) {
+    return new OutlineCallsiteMappingInformation(positions, outline);
   }
 
   public static boolean isSupported(MapVersion version) {
@@ -75,8 +90,7 @@
     if (isSupported(version)) {
       JsonObject postionsMapObject = object.getAsJsonObject(POSITIONS_KEY);
       if (postionsMapObject == null) {
-        throw new CompilationError(
-            "Expected '" + POSITIONS_KEY + "' to be present: " + object.getAsString());
+        throw new CompilationError("Expected '" + POSITIONS_KEY + "' to be present: " + object);
       }
       Int2IntSortedMap positionsMap = new Int2IntLinkedOpenHashMap();
       postionsMapObject
@@ -92,7 +106,26 @@
                   throw new CompilationError("Invalid position entry: " + entry.toString());
                 }
               });
-      onMappingInfo.accept(OutlineCallsiteMappingInformation.create(positionsMap));
+      MethodReference outline = null;
+      JsonElement outlineElement = object.get(OUTLINE_KEY);
+      if (outlineElement != null) {
+        outline = MethodReferenceUtils.methodFromSmali(outlineElement.getAsString());
+      } else if (version.isGreaterThanOrEqualTo(SUPPORTED_WITH_OUTLINE_VERSION)) {
+        throw new CompilationError("Expected '" + OUTLINE_KEY + "' to be present: " + object);
+      }
+      onMappingInfo.accept(OutlineCallsiteMappingInformation.create(positionsMap, outline));
     }
   }
+
+  public void setOutlineInternal(MethodReference outline) {
+    this.outline = outline;
+  }
+
+  public Int2IntSortedMap getPositions() {
+    return positions;
+  }
+
+  public void setPositionsInternal(Int2IntSortedMap positions) {
+    this.positions = positions;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineMappingInformation.java
index 1914431..54c392b 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineMappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineMappingInformation.java
@@ -27,6 +27,11 @@
   }
 
   @Override
+  public OutlineMappingInformation asOutlineMappingInformation() {
+    return this;
+  }
+
+  @Override
   public boolean allowOther(MappingInformation information) {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/references/MethodReference.java b/src/main/java/com/android/tools/r8/references/MethodReference.java
index e0314b8..5ef8596 100644
--- a/src/main/java/com/android/tools/r8/references/MethodReference.java
+++ b/src/main/java/com/android/tools/r8/references/MethodReference.java
@@ -85,6 +85,6 @@
 
   @Override
   public String toString() {
-    return getHolderClass().toString() + getMethodName() + getMethodDescriptor();
+    return getHolderClass() + getMethodName() + getMethodDescriptor();
   }
 }
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 1008d80..90b9dfb 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2820,19 +2820,20 @@
     WorkList<DexType> worklist = WorkList.newIdentityWorkList(type);
     worklist.addIfNotSeen(interfaces);
     while (worklist.hasNext()) {
-      DexClass clazz = appInfo().definitionFor(worklist.next());
-      if (clazz == null) {
-        continue;
-      }
-      if (clazz.isProgramClass()) {
-        markProgramMethodOverridesAsLive(instantiation, clazz.asProgramClass());
-      } else {
-        markLibraryAndClasspathMethodOverridesAsLive(instantiation, clazz);
-      }
-      if (clazz.superType != null) {
-        worklist.addIfNotSeen(clazz.superType);
-      }
-      worklist.addIfNotSeen(clazz.interfaces);
+      ClassResolutionResult classResolutionResult =
+          appInfo().contextIndependentDefinitionForWithResolutionResult(worklist.next());
+      classResolutionResult.forEachClassResolutionResult(
+          clazz -> {
+            if (clazz.isProgramClass()) {
+              markProgramMethodOverridesAsLive(instantiation, clazz.asProgramClass());
+            } else {
+              markLibraryAndClasspathMethodOverridesAsLive(instantiation, clazz);
+            }
+            if (clazz.superType != null) {
+              worklist.addIfNotSeen(clazz.superType);
+            }
+            worklist.addIfNotSeen(clazz.interfaces);
+          });
     }
   }
 
@@ -2973,12 +2974,18 @@
     worklist.addIfNotSeen(instantiatedClass);
     while (worklist.hasNext()) {
       DexProgramClass clazz = worklist.next();
-      DexEncodedMethod override = clazz.lookupVirtualMethod(libraryMethodOverride);
+      ProgramMethod override = clazz.lookupProgramMethod(libraryMethodOverride);
       if (override != null) {
-        if (override.isLibraryMethodOverride().isTrue()) {
+        if (override.getDefinition().isLibraryMethodOverride().isTrue()) {
           continue;
         }
-        override.setLibraryMethodOverride(OptionalBool.TRUE);
+        override.getDefinition().setLibraryMethodOverride(OptionalBool.TRUE);
+        // TODO(b/243483849): The minifier does not detect library overrides if the library class
+        //  is present both as program and library class. We force disable minification here as a
+        //  work-around until this is fixed.
+        if (options.loadAllClassDefinitions) {
+          shouldNotBeMinified(override);
+        }
       }
       clazz.forEachImmediateSupertype(
           superType -> {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index cf357ed..773903d 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -288,7 +288,7 @@
           || parseUnsupportedOptionAndErr(optionStart)) {
         // Intentionally left empty.
       } else if (acceptString("keepkotlinmetadata")) {
-        configurationBuilder.addRule(
+        ProguardKeepRule keepKotlinMetadata =
             ProguardKeepRule.builder()
                 .setType(ProguardKeepRuleType.KEEP)
                 .setClassType(ProguardClassType.CLASS)
@@ -301,7 +301,11 @@
                         .build())
                 .setMemberRules(Collections.singletonList(ProguardMemberRule.defaultKeepAllRule()))
                 .setSource("-keepkotlinmetadata")
-                .build());
+                .build();
+        // Mark the rule as used to ensure we do not report any information messages if the class
+        // is not present.
+        keepKotlinMetadata.markAsUsed();
+        configurationBuilder.addRule(keepKotlinMetadata);
         configurationBuilder.addKeepAttributePatterns(
             Collections.singletonList(RUNTIME_VISIBLE_ANNOTATIONS));
       } else if (acceptString("renamesourcefileattribute")) {
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 37641f0..8760fb8 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.features.FeatureSplitBoundaryOptimizationUtils;
 import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
@@ -387,9 +388,8 @@
         .map(DexEncodedMember::getReference)
         .noneMatch(appInfo::isPinned);
 
-    if (!appInfo
-        .getClassToFeatureSplitMap()
-        .isInSameFeatureOrBothInSameBase(sourceClass, targetClass, appView)) {
+    if (!FeatureSplitBoundaryOptimizationUtils.isSafeForVerticalClassMerging(
+        sourceClass, targetClass, appView)) {
       return false;
     }
     if (appView.appServices().allServiceTypes().contains(sourceClass.type)
diff --git a/src/main/java/com/android/tools/r8/startup/StartupClassBuilder.java b/src/main/java/com/android/tools/r8/startup/StartupClassBuilder.java
new file mode 100644
index 0000000..43a63b4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/startup/StartupClassBuilder.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.startup;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.ClassReference;
+
+/** Interface for providing information about a startup class to the compiler. */
+@Keep
+public interface StartupClassBuilder {
+
+  StartupClassBuilder setClassReference(ClassReference classReference);
+}
diff --git a/src/main/java/com/android/tools/r8/startup/StartupMethodBuilder.java b/src/main/java/com/android/tools/r8/startup/StartupMethodBuilder.java
new file mode 100644
index 0000000..0c1efe2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/startup/StartupMethodBuilder.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.startup;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.MethodReference;
+
+/** Interface for providing information about a startup method to the compiler. */
+@Keep
+public interface StartupMethodBuilder {
+
+  StartupMethodBuilder setMethodReference(MethodReference methodReference);
+}
diff --git a/src/main/java/com/android/tools/r8/startup/StartupProfileBuilder.java b/src/main/java/com/android/tools/r8/startup/StartupProfileBuilder.java
new file mode 100644
index 0000000..d3626a1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/startup/StartupProfileBuilder.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.startup;
+
+import com.android.tools.r8.Keep;
+import java.util.function.Consumer;
+
+/** Interface for providing a startup profile to the compiler. */
+@Keep
+public interface StartupProfileBuilder {
+
+  /** API for adding information about a startup class to the compiler. */
+  StartupProfileBuilder addStartupClass(Consumer<StartupClassBuilder> startupClassBuilderConsumer);
+
+  /** API for adding information about a startup method to the compiler. */
+  StartupProfileBuilder addStartupMethod(
+      Consumer<StartupMethodBuilder> startupMethodBuilderConsumer);
+
+  /**
+   * API for adding information about a synthetic startup method to the compiler.
+   *
+   * <p>When shrinking an app using R8, the names of synthetic classes may differ from the synthetic
+   * names that arise from dexing the app using D8. Therefore, synthetic classes and methods should
+   * not be added to the startup profile using {@link #addStartupClass(Consumer)} and {@link
+   * #addStartupMethod(Consumer)}.
+   *
+   * <p>Instead, synthetic items should be added to the startup profile using this method, which
+   * takes the name of the synthetic context instead of the synthetic name. The addition of the
+   * synthetic context will be interpreted as the presence of any method on any synthetic class that
+   * has been synthesized from the synthetic context.
+   *
+   * <p>Example: Instead of adding "Lcom/example/MainActivity$ExternalSynthetic0;->m()V" as a
+   * (non-synthetic) startup method, a synthetic startup method should be added with synthetic
+   * context "Lcom/example/MainActivity;".
+   *
+   * <p>NOTE: This should only be used when supplying a startup profile that is generated from an
+   * unobfuscated build of the app to R8.
+   */
+  StartupProfileBuilder addSyntheticStartupMethod(
+      Consumer<SyntheticStartupMethodBuilder> syntheticStartupMethodBuilderConsumer);
+}
diff --git a/src/main/java/com/android/tools/r8/startup/StartupProfileProvider.java b/src/main/java/com/android/tools/r8/startup/StartupProfileProvider.java
new file mode 100644
index 0000000..4d98373
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/startup/StartupProfileProvider.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.startup;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.Resource;
+
+/** Interface for providing a startup profile to the compiler. */
+@Keep
+public interface StartupProfileProvider extends Resource {
+
+  // TODO(b/238173796): Change the implementation to use the new API below.
+  /** Return the startup profile. */
+  String get();
+
+  /** Provides the startup profile by callbacks to the given {@param startupProfileBuilder}. */
+  void getStartupProfile(StartupProfileBuilder startupProfileBuilder);
+}
diff --git a/src/main/java/com/android/tools/r8/startup/SyntheticStartupMethodBuilder.java b/src/main/java/com/android/tools/r8/startup/SyntheticStartupMethodBuilder.java
new file mode 100644
index 0000000..281bac7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/startup/SyntheticStartupMethodBuilder.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.startup;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.ClassReference;
+
+/** Interface for providing information about a synthetic startup method to the compiler. */
+@Keep
+public interface SyntheticStartupMethodBuilder {
+
+  SyntheticStartupMethodBuilder setSyntheticContextReference(ClassReference classReference);
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 9aed303..2de90be 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -29,6 +29,7 @@
   public final SyntheticKind API_MODEL_STUB = generator.forGlobalClass();
 
   // Classpath only synthetics in the global type namespace.
+  public final SyntheticKind GENERIC_API_CONVERSION_STUB = generator.forGlobalClasspathClass();
   public final SyntheticKind RETARGET_STUB = generator.forGlobalClasspathClass();
   public final SyntheticKind EMULATED_INTERFACE_MARKER_CLASS = generator.forGlobalClasspathClass();
 
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 d784613..f02405c 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1793,6 +1793,7 @@
   public static class TestingOptions {
 
     public boolean neverReuseCfLocalRegisters = false;
+    public boolean roundtripThroughLIR = false;
     private boolean hasReadCheckDeterminism = false;
     private DeterminismChecker determinismChecker = null;
 
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index a0a2a74..90d26b4 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -57,6 +57,7 @@
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.PositionRangeAllocator;
 import com.android.tools.r8.naming.PositionRangeAllocator.CardinalPositionRangeAllocator;
 import com.android.tools.r8.naming.PositionRangeAllocator.NonCardinalPositionRangeAllocator;
@@ -71,6 +72,7 @@
 import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation;
 import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation.RemoveInnerFramesAction;
 import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation.ThrowsCondition;
+import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.retrace.internal.RetraceUtils;
 import com.android.tools.r8.shaking.KeepInfoCollection;
@@ -674,7 +676,9 @@
           DexMethod outlineMethod = getOutlineMethod(mappedPositions.get(0));
           if (outlineMethod != null) {
             outlinesToFix
-                .computeIfAbsent(outlineMethod, ignored -> new OutlineFixupBuilder())
+                .computeIfAbsent(
+                    outlineMethod,
+                    outline -> new OutlineFixupBuilder(computeMappedMethod(outline, appView)))
                 .setMappedPositionsOutline(mappedPositions);
             methodMappingInfo.add(OutlineMappingInformation.builder().build());
           }
@@ -778,7 +782,8 @@
                   });
               outlinesToFix
                   .computeIfAbsent(
-                      firstPosition.outlineCallee, ignored -> new OutlineFixupBuilder())
+                      firstPosition.outlineCallee,
+                      outline -> new OutlineFixupBuilder(computeMappedMethod(outline, appView)))
                   .addMappedRangeForOutlineCallee(lastMappedRange, positionMap);
             }
             i = j;
@@ -831,6 +836,14 @@
     return false;
   }
 
+  private static MethodReference computeMappedMethod(DexMethod current, AppView<?> appView) {
+    NamingLens namingLens = appView.getNamingLens();
+    DexMethod renamedMethodSignature =
+        namingLens.lookupMethod(
+            appView.graphLens().getRenamedMethodSignature(current), appView.dexItemFactory());
+    return renamedMethodSignature.asMethodReference();
+  }
+
   private static DexMethod getOutlineMethod(MappedPosition mappedPosition) {
     if (mappedPosition.isOutline) {
       return mappedPosition.method;
@@ -1403,12 +1416,17 @@
 
   private static class OutlineFixupBuilder {
 
-    private static int MINIFIED_POSITION_REMOVED = -1;
+    private static final int MINIFIED_POSITION_REMOVED = -1;
 
+    private final MethodReference outlineMethod;
     private List<MappedPosition> mappedOutlinePositions = null;
     private final List<Pair<MappedRange, Int2IntMap>> mappedOutlineCalleePositions =
         new ArrayList<>();
 
+    private OutlineFixupBuilder(MethodReference outlineMethod) {
+      this.outlineMethod = outlineMethod;
+    }
+
     public void setMappedPositionsOutline(List<MappedPosition> mappedPositionsOutline) {
       this.mappedOutlinePositions = mappedPositionsOutline;
     }
@@ -1437,7 +1455,7 @@
               }
             });
         mappedRange.addMappingInformation(
-            OutlineCallsiteMappingInformation.create(map), Unreachable::raise);
+            OutlineCallsiteMappingInformation.create(map, outlineMethod), Unreachable::raise);
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
index 037fafb..8e240d7 100644
--- a/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
@@ -147,4 +147,13 @@
     }
     return builder.append(")").toString();
   }
+
+  public static MethodReference methodFromSmali(String smali) {
+    int holderEndIndex = smali.indexOf(";") + 1;
+    int methodDescriptorIndex = smali.indexOf("(", holderEndIndex);
+    return Reference.methodFromDescriptor(
+        smali.substring(0, holderEndIndex),
+        smali.substring(holderEndIndex, methodDescriptorIndex),
+        smali.substring(methodDescriptorIndex));
+  }
 }
diff --git a/src/test/examplesJava9/stackwalker/Example.java b/src/test/examplesJava9/stackwalker/Example.java
new file mode 100644
index 0000000..6208060
--- /dev/null
+++ b/src/test/examplesJava9/stackwalker/Example.java
@@ -0,0 +1,30 @@
+// 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 stackwalker;
+
+import java.lang.StackWalker.StackFrame;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class Example {
+  public static void main(String[] args) {
+    List<String> OneFrameStack =
+        StackWalker.getInstance()
+            .walk(s -> s.limit(7).map(StackFrame::getMethodName).collect(Collectors.toList()));
+    System.out.println(OneFrameStack);
+    frame1();
+  }
+
+  public static void frame1() {
+    frame2();
+  }
+
+  public static void frame2() {
+    List<String> ThreeFrameStack =
+        StackWalker.getInstance()
+            .walk(s -> s.limit(7).map(StackFrame::getMethodName).collect(Collectors.toList()));
+    System.out.println(ThreeFrameStack);
+  }
+}
diff --git a/src/test/examplesJava9/timeunit/Example.java b/src/test/examplesJava9/timeunit/Example.java
new file mode 100644
index 0000000..09e1d0c
--- /dev/null
+++ b/src/test/examplesJava9/timeunit/Example.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 timeunit;
+
+import java.time.temporal.ChronoUnit;
+import java.util.concurrent.TimeUnit;
+
+public class Example {
+
+  public static void main(String[] args) {
+    TimeUnit timeUnit = TimeUnit.of(ChronoUnit.NANOS);
+    System.out.println(timeUnit.toChronoUnit());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/L8TestBuilder.java b/src/test/java/com/android/tools/r8/L8TestBuilder.java
index 3186dc1..3050989 100644
--- a/src/test/java/com/android/tools/r8/L8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/L8TestBuilder.java
@@ -8,6 +8,8 @@
 import static junit.framework.TestCase.assertTrue;
 
 import com.android.tools.r8.TestBase.Backend;
+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.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidAppConsumers;
@@ -23,6 +25,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 
@@ -195,6 +198,7 @@
     if (programConsumer != null) {
       return null;
     }
+    assertNoUnexpectedDiagnosticMessages();
     return new L8TestCompileResult(
             sink.build(),
             apiLevel,
@@ -203,16 +207,53 @@
             mapping,
             state,
             backend.isCf() ? OutputMode.ClassFile : OutputMode.DexIndexed)
-        .applyIf(
-            finalPrefixVerification,
-            compileResult ->
-                compileResult.inspect(
-                    inspector ->
-                        inspector.forAllClasses(
-                            clazz ->
-                                assertTrue(
-                                    clazz.getFinalName().startsWith("j$.")
-                                        || clazz.getFinalName().startsWith("java.")))));
+        .applyIf(finalPrefixVerification, this::validatePrefix);
+  }
+
+  private void validatePrefix(L8TestCompileResult compileResult) throws IOException {
+    InternalOptions options = new InternalOptions();
+    DesugaredLibrarySpecification specification =
+        DesugaredLibrarySpecificationParser.parseDesugaredLibrarySpecification(
+            this.desugaredLibrarySpecification,
+            options.dexItemFactory(),
+            options.reporter,
+            true,
+            apiLevel.getLevel());
+    Set<String> maintainTypeOrPrefix = specification.getMaintainTypeOrPrefixForTesting();
+    compileResult.inspect(
+        inspector ->
+            inspector.forAllClasses(
+                clazz -> {
+                  String finalName = clazz.getFinalName();
+                  if (finalName.startsWith("java.")) {
+                    assertTrue(maintainTypeOrPrefix.stream().anyMatch(finalName::startsWith));
+                  } else {
+                    assertTrue(finalName.startsWith("j$."));
+                  }
+                }));
+  }
+
+  private void assertNoUnexpectedDiagnosticMessages() {
+    TestDiagnosticMessages diagnosticsMessages = state.getDiagnosticsMessages();
+    diagnosticsMessages.assertNoErrors();
+    List<Diagnostic> warnings = diagnosticsMessages.getWarnings();
+    // We allow warnings exclusively when using the extended version for JDK11 testing.
+    // In this case, all warnings should apply to org.testng.Assert types which are not present
+    // in the vanilla desugared library.
+    // Vanilla desugared library compilation should have no warnings.
+    assertTrue(
+        warnings.isEmpty()
+            || warnings.stream()
+                .allMatch(warn -> warn.getDiagnosticMessage().contains("org.testng.Assert")));
+    List<Diagnostic> infos = diagnosticsMessages.getInfos();
+    // The rewriting confuses the generic signatures in some methods. Such signatures are never
+    // used by tools (they use the non library desugared version) and are stripped when compiling
+    // with R8 anyway.
+    // TODO(b/243483320): Investigate the Invalid signature.
+    assertTrue(
+        infos.isEmpty()
+            || infos.stream()
+                .allMatch(info -> info.getDiagnosticMessage().contains("Invalid signature ")));
   }
 
   private L8Command.Builder addProgramClassFileData(L8Command.Builder builder) {
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index 4150236..52d84fc 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -7,7 +7,6 @@
 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.errors.UnsupportedFeatureDiagnostic;
 import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index d948ad2..f31c1ae 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -619,7 +619,7 @@
       ImmutableMap.<DexVm, String>builder()
           .put(DexVm.ART_DEFAULT, "art")
           .put(DexVm.ART_MASTER_HOST, "host/art-master")
-          .put(DexVm.ART_13_0_0_HOST, "host/art-13-dev")
+          .put(DexVm.ART_13_0_0_HOST, "host/art-13.0.0")
           .put(DexVm.ART_12_0_0_HOST, "host/art-12.0.0-beta4")
           .put(DexVm.ART_10_0_0_HOST, "art-10.0.0")
           .put(DexVm.ART_9_0_0_HOST, "art-9.0.0")
diff --git a/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/L8Benchmark.java b/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/L8Benchmark.java
index 46f8916..9095f727 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/L8Benchmark.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/L8Benchmark.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.benchmarks.desugaredlib;
 
+import static com.android.tools.r8.ToolHelper.getDesugarLibConversions;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.CustomConversionVersion.LATEST;
+
 import com.android.tools.r8.L8TestBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestState;
@@ -61,7 +64,7 @@
     LibraryDesugaringSpecification spec =
         new LibraryDesugaringSpecification(
             "JDK11_Benchmark",
-            ImmutableSet.of(undesugarJdkLib),
+            ImmutableSet.of(undesugarJdkLib, getDesugarLibConversions(LATEST)),
             Paths.get("src/library_desugar/jdk11/desugar_jdk_libs.json"),
             ImmutableSet.of(androidJar.getRoot(environment).resolve("android.jar")),
             LibraryDesugaringSpecification.JDK11_DESCRIPTOR,
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingWithStartupClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingWithStartupClassesTest.java
index 417a097..28f7855 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingWithStartupClassesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingWithStartupClassesTest.java
@@ -5,12 +5,15 @@
 package com.android.tools.r8.classmerging.horizontal;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.StartupProfileProvider;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.experimental.startup.StartupClass;
 import com.android.tools.r8.experimental.startup.StartupProfile;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.startup.StartupProfileBuilder;
+import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collections;
@@ -63,7 +66,23 @@
                                                       toDexType(startupClass, dexItemFactory))
                                                   .build())))
                       .build();
-              StartupProfileProvider startupProfileProvider = startupProfile::serializeToString;
+              StartupProfileProvider startupProfileProvider =
+                  new StartupProfileProvider() {
+                    @Override
+                    public String get() {
+                      return startupProfile.serializeToString();
+                    }
+
+                    @Override
+                    public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) {
+                      throw new Unimplemented();
+                    }
+
+                    @Override
+                    public Origin getOrigin() {
+                      return Origin.unknown();
+                    }
+                  };
               options.getStartupOptions().setStartupProfileProvider(startupProfileProvider);
             })
         .addHorizontallyMergedClassesInspector(
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java
index 33aab91..7a10578 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java
@@ -7,6 +7,7 @@
 
 import com.android.tools.r8.compilerapi.mockdata.MockClass;
 import com.android.tools.r8.compilerapi.mockdata.MockClassWithAssertion;
+import com.android.tools.r8.compilerapi.mockdata.PostStartupMockClass;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -75,6 +76,10 @@
     return MockClassWithAssertion.class;
   }
 
+  public Class<?> getPostStartupMockClass() {
+    return PostStartupMockClass.class;
+  }
+
   public Path getJava8RuntimeJar() {
     return Paths.get("third_party", "openjdk", "openjdk-rt-1.8", "rt.jar");
   }
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 14a04dc..6d12c20 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -17,7 +17,9 @@
 import com.android.tools.r8.compilerapi.mapid.CustomMapIdTest;
 import com.android.tools.r8.compilerapi.mockdata.MockClass;
 import com.android.tools.r8.compilerapi.mockdata.MockClassWithAssertion;
+import com.android.tools.r8.compilerapi.mockdata.PostStartupMockClass;
 import com.android.tools.r8.compilerapi.sourcefile.CustomSourceFileTest;
+import com.android.tools.r8.compilerapi.startupprofile.StartupProfileApiTest;
 import com.android.tools.r8.compilerapi.testsetup.ApiTestingSetUpTest;
 import com.android.tools.r8.compilerapi.wrappers.CommandLineParserTest;
 import com.android.tools.r8.compilerapi.wrappers.EnableMissingLibraryApiModelingTest;
@@ -41,16 +43,16 @@
           CustomSourceFileTest.ApiTest.class,
           AssertionConfigurationTest.ApiTest.class,
           InputDependenciesTest.ApiTest.class,
-          DesugarDependenciesTest.ApiTest.class);
-
-  private static final List<Class<? extends CompilerApiTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
-      ImmutableList.of(
+          DesugarDependenciesTest.ApiTest.class,
           GlobalSyntheticsTest.ApiTest.class,
           CommandLineParserTest.ApiTest.class,
           EnableMissingLibraryApiModelingTest.ApiTest.class,
           AndroidPlatformBuildApiTest.ApiTest.class,
           UnsupportedFeaturesDiagnosticApiTest.ApiTest.class);
 
+  private static final List<Class<? extends CompilerApiTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
+      ImmutableList.of(StartupProfileApiTest.ApiTest.class);
+
   private final TemporaryFolder temp;
 
   public CompilerApiTestCollection(TemporaryFolder temp) {
@@ -74,7 +76,11 @@
 
   @Override
   public List<Class<?>> getAdditionalClassesForTests() {
-    return ImmutableList.of(CompilerApiTest.class, MockClass.class, MockClassWithAssertion.class);
+    return ImmutableList.of(
+        CompilerApiTest.class,
+        MockClass.class,
+        MockClassWithAssertion.class,
+        PostStartupMockClass.class);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollectionTest.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollectionTest.java
index 0aae1dd..a2d7693 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollectionTest.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollectionTest.java
@@ -36,7 +36,7 @@
   /**
    * If this test fails the test.jar needs to be regenerated and uploaded to cloud storage.
    *
-   * <p>See: {@code CompilerApiTestCollection.main} to regenerate.
+   * <p>See: {@link CompilerApiTestCollectionTest#main} to regenerate.
    *
    * <p>To preserve compatibility, make sure only to regenerate together with test changes and with
    * NO changes to the compiler itself.
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestRunner.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestRunner.java
index ee135e8..0ef8064 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestRunner.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestRunner.java
@@ -15,9 +15,8 @@
  * Base runner for all compiler API tests.
  *
  * <p>Using this runner will automatically create an externalized variant of the test. That is
- * useful to more quickely ensure the test itself is not using resources that are not available.
- * Note however, that it does not prevent using non-kept code in the compilers unless testing with
- * r8lib!
+ * useful to more quickly ensure the test itself is not using resources that are not available. Note
+ * however, that it does not prevent using non-kept code in the compilers unless testing with r8lib!
  */
 @RunWith(Parameterized.class)
 public abstract class CompilerApiTestRunner extends TestBase {
diff --git a/src/test/java/com/android/tools/r8/compilerapi/mockdata/PostStartupMockClass.java b/src/test/java/com/android/tools/r8/compilerapi/mockdata/PostStartupMockClass.java
new file mode 100644
index 0000000..6529022
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/mockdata/PostStartupMockClass.java
@@ -0,0 +1,8 @@
+// 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.mockdata;
+
+// Class to use as data for the compilation.
+public class PostStartupMockClass {}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/startupprofile/StartupProfileApiTest.java b/src/test/java/com/android/tools/r8/compilerapi/startupprofile/StartupProfileApiTest.java
new file mode 100644
index 0000000..f8bc404
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/startupprofile/StartupProfileApiTest.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.compilerapi.startupprofile;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.startup.StartupProfileBuilder;
+import com.android.tools.r8.startup.StartupProfileProvider;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.function.BiConsumer;
+import org.junit.Test;
+
+public class StartupProfileApiTest extends CompilerApiTestRunner {
+
+  private static final int FIRST_API_LEVEL_WITH_NATIVE_MULTIDEX = 21;
+
+  public StartupProfileApiTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<? extends CompilerApiTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  @Test
+  public void testD8ArrayApi() throws Exception {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    runTest(
+        test,
+        programConsumer ->
+            test.runD8(ApiTest::addStartupProfileProviderUsingArrayApi, programConsumer));
+  }
+
+  @Test
+  public void testD8CollectionApi() throws Exception {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    runTest(
+        test,
+        programConsumer ->
+            test.runD8(ApiTest::addStartupProfileProviderUsingCollectionApi, programConsumer));
+  }
+
+  @Test
+  public void testR8ArrayApi() throws Exception {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    runTest(
+        test,
+        programConsumer ->
+            test.runR8(ApiTest::addStartupProfileProviderUsingArrayApi, programConsumer));
+  }
+
+  @Test
+  public void testR8CollectionApi() throws Exception {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    runTest(
+        test,
+        programConsumer ->
+            test.runR8(ApiTest::addStartupProfileProviderUsingCollectionApi, programConsumer));
+  }
+
+  private void runTest(ApiTest test, ThrowingConsumer<ProgramConsumer, Exception> testRunner)
+      throws Exception {
+    Path output = temp.newFolder().toPath();
+    testRunner.accept(new DexIndexedConsumer.DirectoryConsumer(output));
+    assertThat(
+        new CodeInspector(output.resolve("classes.dex")).clazz(test.getMockClass()), isPresent());
+    // TODO(b/238173796): The PostStartupMockClass should be in classes2.dex.
+    assertThat(
+        new CodeInspector(output.resolve("classes.dex")).clazz(test.getPostStartupMockClass()),
+        isPresent());
+  }
+
+  public static class ApiTest extends CompilerApiTest {
+
+    public ApiTest(Object parameters) {
+      super(parameters);
+    }
+
+    private StartupProfileProvider getStartupProfileProvider() {
+      return new StartupProfileProvider() {
+        @Override
+        public String get() {
+          // Intentionally empty. All uses of this API should be rewritten to use getStartupProfile.
+          return "";
+        }
+
+        @Override
+        public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) {
+          startupProfileBuilder.addStartupClass(
+              startupClassBuilder ->
+                  startupClassBuilder.setClassReference(Reference.classFromClass(getMockClass())));
+        }
+
+        @Override
+        public Origin getOrigin() {
+          return Origin.unknown();
+        }
+      };
+    }
+
+    public void runD8(
+        BiConsumer<D8Command.Builder, StartupProfileProvider> startupProfileProviderInstaller,
+        ProgramConsumer programConsumer)
+        throws Exception {
+      D8Command.Builder commandBuilder =
+          D8Command.builder()
+              .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown())
+              .addClassProgramData(getBytesForClass(getPostStartupMockClass()), Origin.unknown())
+              .addLibraryFiles(getJava8RuntimeJar())
+              .setMinApiLevel(FIRST_API_LEVEL_WITH_NATIVE_MULTIDEX)
+              .setProgramConsumer(programConsumer);
+      startupProfileProviderInstaller.accept(commandBuilder, getStartupProfileProvider());
+      D8.run(commandBuilder.build());
+    }
+
+    public void runR8(
+        BiConsumer<R8Command.Builder, StartupProfileProvider> startupProfileProviderInstaller,
+        ProgramConsumer programConsumer)
+        throws Exception {
+      R8Command.Builder commandBuilder =
+          R8Command.builder()
+              .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown())
+              .addClassProgramData(getBytesForClass(getPostStartupMockClass()), Origin.unknown())
+              .addProguardConfiguration(
+                  Collections.singletonList("-keep class * { *; }"), Origin.unknown())
+              .addLibraryFiles(getJava8RuntimeJar())
+              .setMinApiLevel(FIRST_API_LEVEL_WITH_NATIVE_MULTIDEX)
+              .setProgramConsumer(programConsumer);
+      startupProfileProviderInstaller.accept(commandBuilder, getStartupProfileProvider());
+      R8.run(commandBuilder.build());
+    }
+
+    @Test
+    public void testD8ArrayApi() throws Exception {
+      runD8(ApiTest::addStartupProfileProviderUsingArrayApi, DexIndexedConsumer.emptyConsumer());
+    }
+
+    private static void addStartupProfileProviderUsingArrayApi(
+        D8Command.Builder commandBuilder, StartupProfileProvider startupProfileProvider) {
+      StartupProfileProvider[] startupProfileProviders =
+          new StartupProfileProvider[] {startupProfileProvider};
+      commandBuilder.addStartupProfileProviders(startupProfileProviders);
+    }
+
+    @Test
+    public void testD8CollectionApi() throws Exception {
+      runD8(
+          ApiTest::addStartupProfileProviderUsingCollectionApi, DexIndexedConsumer.emptyConsumer());
+    }
+
+    private static void addStartupProfileProviderUsingCollectionApi(
+        D8Command.Builder commandBuilder, StartupProfileProvider startupProfileProvider) {
+      Collection<StartupProfileProvider> startupProfileProviders =
+          Collections.singleton(startupProfileProvider);
+      commandBuilder.addStartupProfileProviders(startupProfileProviders);
+    }
+
+    @Test
+    public void testR8ArrayApi() throws Exception {
+      runR8(ApiTest::addStartupProfileProviderUsingArrayApi, DexIndexedConsumer.emptyConsumer());
+    }
+
+    private static void addStartupProfileProviderUsingArrayApi(
+        R8Command.Builder commandBuilder, StartupProfileProvider startupProfileProvider) {
+      StartupProfileProvider[] startupProfileProviders =
+          new StartupProfileProvider[] {startupProfileProvider};
+      commandBuilder.addStartupProfileProviders(startupProfileProviders);
+    }
+
+    @Test
+    public void testR8CollectionApi() throws Exception {
+      runR8(
+          ApiTest::addStartupProfileProviderUsingCollectionApi, DexIndexedConsumer.emptyConsumer());
+    }
+
+    private static void addStartupProfileProviderUsingCollectionApi(
+        R8Command.Builder commandBuilder, StartupProfileProvider startupProfileProvider) {
+      Collection<StartupProfileProvider> startupProfileProviders =
+          Collections.singleton(startupProfileProvider);
+      commandBuilder.addStartupProfileProviders(startupProfileProviders);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassV2Test.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassV2Test.java
index 102902a..8d84ddd 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassV2Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassV2Test.java
@@ -66,7 +66,7 @@
     testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
         .addInnerClasses(getClass())
         .addKeepMainRule(Executor.class)
-        .setMode(compilationMode)
+        .overrideCompilationMode(compilationMode)
         .addOptionsModification(
             options -> {
               // Devirtualizing is correcting the invalid member-rebinding.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
index d17c851..2838f2c 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
@@ -6,8 +6,9 @@
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
+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 com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.startsWith;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -46,7 +47,7 @@
   public static List<Object[]> data() {
     return buildParameters(
         getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
-        getJdk8Jdk11(),
+        ImmutableList.of(JDK8, JDK11, JDK11_PATH),
         ImmutableList.of(D8_L8DEBUG));
   }
 
@@ -65,6 +66,7 @@
     testForL8(parameters.getApiLevel())
         .apply(libraryDesugaringSpecification::configureL8TestBuilder)
         .compile()
+        .assertNoMessages()
         .inspect(this::assertCorrect);
   }
 
@@ -108,7 +110,9 @@
                 assertThat(
                     clazz.getOriginalName(),
                     CoreMatchers.anyOf(startsWith("j$."), startsWith("java."))));
-    assertThat(inspector.clazz("j$.time.Clock"), isPresent());
+    if (parameters.getApiLevel().getLevel() <= AndroidApiLevel.R.getLevel()) {
+      assertThat(inspector.clazz("j$.time.Clock"), isPresent());
+    }
     // Above N the following classes are removed instead of being desugared.
     if (parameters.getApiLevel().getLevel() >= AndroidApiLevel.N.getLevel()) {
       assertFalse(inspector.clazz("j$.util.Optional").isPresent());
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 3737814..c572169 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
@@ -117,8 +117,11 @@
           "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()");
+      ImmutableSet.of(
+          "java.util.Set java.util.stream.Collector.characteristics()",
+          "java.lang.Object java.lang.StackWalker.walk(java.util.function.Function)");
 
   // TODO(b/238179854): Investigate how to fix these.
   private static final Set<String> MISSING_GENERIC_TYPE_CONVERSION_PATH =
@@ -147,7 +150,7 @@
 
   // TODO: parameterize to check both api<=23 as well as 23<api<26 for which the spec differs.
   private final AndroidApiLevel minApi = AndroidApiLevel.B;
-  private final AndroidApiLevel targetApi = AndroidApiLevel.S;
+  private final AndroidApiLevel targetApi = AndroidApiLevel.MASTER;
 
   private Set<String> getMissingGenericTypeConversions() {
     HashSet<String> missing = new HashSet<>(MISSING_GENERIC_TYPE_CONVERSION);
@@ -407,6 +410,11 @@
               indirectWrappers.computeIfAbsent(t, k -> new HashSet<>()).add(reference);
             }
           };
+      if (clazz.getAccessFlags().isEnum()) {
+        // Enum are not really wrapped, instead, each instance is converted to the matching
+        // instance, so there is no need to wrap indirect parameters and return types.
+        continue;
+      }
       clazz.forAllVirtualMethods(
           method -> {
             assertTrue(method.toString(), method.isPublic() || method.isProtected());
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 c9c9a31..3309c38 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
@@ -16,6 +16,7 @@
 import java.nio.file.Files;
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.nio.file.StandardOpenOption;
 import java.util.Map;
 import java.util.zip.ZipEntry;
@@ -34,10 +35,21 @@
           .put("java/lang/DesugarMath", "java/lang/Math")
           .put("java/io/DesugarBufferedReader", "java/io/BufferedReader")
           .put("java/io/DesugarInputStream", "java/io/InputStream")
-          .put("wrapper/adapter/HybridFileSystemProvider", "java/adapter/HybridFileSystemProvider")
-          .put("wrapper/adapter/HybridFileTypeDetector", "java/adapter/HybridFileTypeDetector")
+          .put("sun/misc/DesugarUnsafe", "jdk/internal/misc/Unsafe")
+          .put("wrapper/adapter/HybridFileSystemProvider", "sun/nio/fs/DefaultFileSystemProvider")
+          .put("wrapper/adapter/HybridFileTypeDetector", "sun/nio/fs/DefaultFileTypeDetector")
           .build();
 
+  public static void main(String[] args) {
+    if (!Files.exists(Paths.get(args[0]))) {
+      throw new RuntimeException("Undesugarer source not found");
+    }
+    if (Files.exists(Paths.get(args[1]))) {
+      throw new RuntimeException("Undesugarer destination already exists");
+    }
+    generateUndesugaredJar(Paths.get(args[0]), Paths.get(args[1]));
+  }
+
   public static Path undesugaredJarJDK11(Path undesugarFolder, Path jdk11Jar) {
     String fileName = jdk11Jar.getFileName().toString();
     String newFileName = fileName.substring(0, fileName.length() - 4) + "_undesugared.jar";
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StackWalkerTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StackWalkerTest.java
new file mode 100644
index 0000000..920798f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StackWalkerTest.java
@@ -0,0 +1,91 @@
+// 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.jdk11;
+
+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_MINIMAL;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+
+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.DesugaredLibraryTestBase;
+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;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StackWalkerTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
+
+  private static final Path INPUT_JAR =
+      Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR + "stackwalker.jar");
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines("[main]", "[frame2, frame1, main]");
+  private static final String EXPECTED_OUTPUT_R8 = StringUtils.lines("[main]", "[main]");
+  private static final String MAIN_CLASS = "stackwalker.Example";
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            .withDexRuntime(Version.MASTER)
+            .withApiLevel(AndroidApiLevel.B)
+            .withApiLevel(AndroidApiLevel.T)
+            .build(),
+        ImmutableList.of(JDK11_MINIMAL, JDK11, JDK11_PATH),
+        DEFAULT_SPECIFICATIONS);
+  }
+
+  public StackWalkerTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
+    this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    Assume.assumeTrue(
+        "Run only once",
+        compilationSpecification == D8_L8DEBUG && libraryDesugaringSpecification == JDK11);
+    // No desugared library, this should work.
+    testForD8()
+        .setMinApi(parameters.getApiLevel())
+        .addProgramFiles(INPUT_JAR)
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testDesugaredLibrary() throws Throwable {
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addProgramFiles(INPUT_JAR)
+        .addKeepMainRule(MAIN_CLASS)
+        .overrideLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.MASTER))
+        // Missing class java.lang.StackWalker$StackFrame.
+        .addOptionsModification(opt -> opt.ignoreMissingClasses = true)
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(
+            compilationSpecification.isProgramShrink() ? EXPECTED_OUTPUT_R8 : EXPECTED_OUTPUT);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StreamCollectorCharacteristicsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StreamCollectorCharacteristicsTest.java
index 56bfcd6..0d44dc3 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StreamCollectorCharacteristicsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StreamCollectorCharacteristicsTest.java
@@ -51,7 +51,7 @@
   }
 
   @Test
-  public void test() throws Exception {
+  public void test() throws Throwable {
     testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
         .addProgramClasses(TestClass.class)
         .addKeepMainRule(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/TimeUnitTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/TimeUnitTest.java
new file mode 100644
index 0000000..54b3bc9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/TimeUnitTest.java
@@ -0,0 +1,67 @@
+// 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.jdk11;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
+
+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.DesugaredLibraryTestBase;
+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;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class TimeUnitTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
+
+  private static final Path INPUT_JAR =
+      Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR + "timeunit.jar");
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("Nanos");
+  private static final String MAIN_CLASS = "timeunit.Example";
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            .withDexRuntimesStartingFromIncluding(Version.V13_0_0)
+            .withApiLevel(AndroidApiLevel.B)
+            .build(),
+        getJdk8Jdk11(),
+        DEFAULT_SPECIFICATIONS);
+  }
+
+  public TimeUnitTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
+    this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addProgramFiles(INPUT_JAR)
+        .overrideLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addKeepMainRule(MAIN_CLASS)
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
index cd5c568..468494f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineTopLevelFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MultiAPILevelMachineDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.DesugaredLibraryConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.HumanToMachineSpecificationConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.LegacyToHumanSpecificationConverter;
 import com.android.tools.r8.origin.Origin;
@@ -41,6 +42,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.util.Map;
 import org.junit.Assume;
 import org.junit.Test;
@@ -110,6 +112,37 @@
   }
 
   @Test
+  public void testMultiLevelLegacyUsingMain() throws IOException {
+    LibraryDesugaringSpecification legacySpec = LibraryDesugaringSpecification.JDK8;
+    testMultiLevelUsingMain(legacySpec);
+  }
+
+  @Test
+  public void testMultiLevelHumanUsingMain() throws IOException {
+    LibraryDesugaringSpecification humanSpec = LibraryDesugaringSpecification.JDK11;
+    testMultiLevelUsingMain(humanSpec);
+  }
+
+  private void testMultiLevelUsingMain(LibraryDesugaringSpecification spec) throws IOException {
+    Assume.assumeTrue(ToolHelper.isLocalDevelopment());
+
+    Path output = temp.newFile().toPath();
+    DesugaredLibraryConverter.convertMultiLevelAnythingToMachineSpecification(
+        spec.getSpecification(), spec.getDesugarJdkLibs(), spec.getLibraryFiles(), output);
+
+    InternalOptions options = new InternalOptions();
+    MachineDesugaredLibrarySpecification machineSpecParsed =
+        new MachineDesugaredLibrarySpecificationParser(
+                options.dexItemFactory(),
+                options.reporter,
+                true,
+                AndroidApiLevel.B.getLevel(),
+                new SyntheticNaming())
+            .parse(StringResource.fromFile(output));
+    assertFalse(machineSpecParsed.getRewriteType().isEmpty());
+  }
+
+  @Test
   public void testSingleLevel() throws IOException {
     Assume.assumeTrue(ToolHelper.isLocalDevelopment());
 
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 46d0b73..380f2d5 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
@@ -48,6 +48,7 @@
   private String l8ExtraKeepRules = "";
   private Consumer<InternalOptions> l8OptionModifier = ConsumerUtils.emptyConsumer();
   private boolean l8FinalPrefixVerification = true;
+  private boolean overrideDefaultLibraryFiles = false;
 
   private CustomLibrarySpecification customLibrarySpecification = null;
   private TestingKeepRuleConsumer keepRuleConsumer = null;
@@ -67,7 +68,6 @@
 
   private void setUp() {
     builder
-        .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
         .setMinApi(parameters.getApiLevel())
         .setMode(compilationSpecification.getProgramCompilationMode());
     LibraryDesugaringTestConfiguration.Builder libraryConfBuilder =
@@ -149,6 +149,17 @@
     return this;
   }
 
+  /**
+   * By default the compilation uses as library libraryDesugaringSpecification.getLibraryFiles(),
+   * which is android.jar at the required compilation api level. Use this Api to set different
+   * library files.
+   */
+  public DesugaredLibraryTestBuilder<T> overrideLibraryFiles(Path... files) {
+    overrideDefaultLibraryFiles = true;
+    builder.addLibraryFiles(files);
+    return this;
+  }
+
   public DesugaredLibraryTestBuilder<T> addProgramFiles(Collection<Path> files) {
     builder.addProgramFiles(files);
     return this;
@@ -165,7 +176,12 @@
     return this;
   }
 
-  public DesugaredLibraryTestBuilder<T> setMode(CompilationMode mode) {
+  /**
+   * By default the compilation uses libraryDesugaringSpecification.getProgramCompilationMode()
+   * which maps to the studio set-up: D8-debug, D8-release and R8-release. Use this Api to set a
+   * different compilation mode.
+   */
+  public DesugaredLibraryTestBuilder<T> overrideCompilationMode(CompilationMode mode) {
     builder.setMode(mode);
     return this;
   }
@@ -311,13 +327,22 @@
     return this;
   }
 
+  private void prepareCompilation() {
+    if (overrideDefaultLibraryFiles) {
+      return;
+    }
+    builder.addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles());
+  }
+
   public DesugaredLibraryTestCompileResult<T> compile() throws Exception {
+    prepareCompilation();
     TestCompileResult<?, ? extends SingleTestRunResult<?>> compile = builder.compile();
     return internalCompile(compile);
   }
 
   public DesugaredLibraryTestCompileResult<T> compileWithExpectedDiagnostics(
       DiagnosticsConsumer consumer) throws Exception {
+    prepareCompilation();
     TestCompileResult<?, ? extends SingleTestRunResult<?>> compile =
         builder.compileWithExpectedDiagnostics(consumer);
     return internalCompile(compile);
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 8b8118f..3ec019a 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
@@ -118,7 +118,7 @@
       new LibraryDesugaringSpecification(
           "JDK11_PATH",
           getUndesugaredJdk11LibJarForTesting(),
-          "jdk11/desugar_jdk_libs_path.json",
+          "jdk11/desugar_jdk_libs_nio.json",
           AndroidApiLevel.R,
           JDK11_PATH_DESCRIPTOR,
           LATEST);
@@ -128,7 +128,7 @@
       new LibraryDesugaringSpecification(
           "JDK11_PATH_ALTERNATIVE_3",
           getUndesugaredJdk11LibJarForTesting(),
-          "jdk11/desugar_jdk_libs_path_alternative_3.json",
+          "jdk11/desugar_jdk_libs_nio_alternative_3.json",
           AndroidApiLevel.R,
           JDK11_PATH_DESCRIPTOR,
           LATEST);
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1719Test.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1719Test.java
index e293b6e..a44f221 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1719Test.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1719Test.java
@@ -21,13 +21,13 @@
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.ResourceException;
-import com.android.tools.r8.StartupProfileProvider;
 import com.android.tools.r8.StringConsumer.FileConsumer;
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.tracereferences.TraceReferences;
 import com.android.tools.r8.tracereferences.TraceReferencesCommand;
 import com.android.tools.r8.tracereferences.TraceReferencesKeepRules;
diff --git a/src/test/java/com/android/tools/r8/internal/startup/ChromeStartupTest.java b/src/test/java/com/android/tools/r8/internal/startup/ChromeStartupTest.java
index 89b37d0..c7211db 100644
--- a/src/test/java/com/android/tools/r8/internal/startup/ChromeStartupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/startup/ChromeStartupTest.java
@@ -13,13 +13,16 @@
 import com.android.tools.r8.ArchiveProgramResourceProvider;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.R8FullTestBuilder;
-import com.android.tools.r8.StartupProfileProvider;
 import com.android.tools.r8.StringResource;
 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.ToolHelper;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.startup.StartupProfileBuilder;
+import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.ZipUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -236,8 +239,24 @@
       Path outDirectory)
       throws Exception {
     StartupProfileProvider startupProfileProvider =
-        StringResource.fromFile(chromeDirectory.resolve("startup.txt"))
-            ::getStringWithRuntimeException;
+        new StartupProfileProvider() {
+          @Override
+          public String get() {
+            return StringResource.fromFile(chromeDirectory.resolve("startup.txt"))
+                .getStringWithRuntimeException();
+          }
+
+          @Override
+          public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) {
+            throw new Unimplemented();
+          }
+
+          @Override
+          public Origin getOrigin() {
+            return Origin.unknown();
+          }
+        };
+
     buildR8(
         testBuilder ->
             testBuilder.addOptionsModification(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/ThrowingInstructionBeforeOtherwiseRedundantInstanceStoreTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/ThrowingInstructionBeforeOtherwiseRedundantInstanceStoreTest.java
index 29ab7c9..4c3f07d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/ThrowingInstructionBeforeOtherwiseRedundantInstanceStoreTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/ThrowingInstructionBeforeOtherwiseRedundantInstanceStoreTest.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -37,11 +36,7 @@
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
-        .applyIf(
-            // See b/229706824.
-            parameters.getDexRuntimeVersion().equals(Version.V13_0_0),
-            r -> r.assertSuccessWithOutputLines("0"),
-            r -> r.assertSuccessWithOutputLines("1"));
+        .assertSuccessWithOutputLines("1");
   }
 
   static class Main {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/ThrowingInstructionBeforeOtherwiseRedundantStaticStoreTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/ThrowingInstructionBeforeOtherwiseRedundantStaticStoreTest.java
index abf3664..60a0b34 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/ThrowingInstructionBeforeOtherwiseRedundantStaticStoreTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/ThrowingInstructionBeforeOtherwiseRedundantStaticStoreTest.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -37,11 +36,7 @@
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
-        .applyIf(
-            // See b/229706824.
-            parameters.getDexRuntimeVersion().equals(Version.V13_0_0),
-            r -> r.assertSuccessWithOutputLines("0"),
-            r -> r.assertSuccessWithOutputLines("1"));
+        .assertSuccessWithOutputLines("1");
   }
 
   static class Main {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
index dfc31a7..5a3960e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
@@ -86,7 +85,7 @@
             // Keep UtilKt#comma*Join*(). Let R8 optimize (inline) others, such as joinOf*(String).
             .addKeepRules("-keep class **.UtilKt")
             .addKeepRules("-keepclassmembers class * { ** comma*Join*(...); }")
-            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addKeepKotlinMetadata()
             .compile()
             .inspect(this::inspectMerged)
             .writeToZip();
@@ -130,7 +129,7 @@
             .addKeepRules("-keepclassmembers class * { ** comma*Join*(...); }")
             // Keep yet rename joinOf*(String).
             .addKeepRules("-keepclassmembers,allowobfuscation class * { ** joinOf*(...); }")
-            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addKeepKotlinMetadata()
             .compile()
             .inspect(this::inspectRenamed)
             .writeToZip();
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 0602eb3..1273bfd 100644
--- a/src/test/java/com/android/tools/r8/lightir/LIRBasicCallbackTest.java
+++ b/src/test/java/com/android/tools/r8/lightir/LIRBasicCallbackTest.java
@@ -11,7 +11,11 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.IntBox;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.IRMetadata;
+import com.android.tools.r8.references.Reference;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -30,10 +34,18 @@
 
   @Test
   public void test() throws Exception {
-    LIRCode code = LIRCode.builder().addConstNull().addConstInt(42).build();
-
-    // State to keep track of position in the byte array as we don't expose this in the iterator.
-    IntBox offset = new IntBox(0);
+    DexItemFactory factory = new DexItemFactory();
+    DexMethod method = factory.createMethod(Reference.methodFromDescriptor("LFoo;", "bar", "()V"));
+    LIRCode code =
+        LIRCode.builder(
+                method,
+                v -> {
+                  throw new Unreachable();
+                })
+            .setMetadata(IRMetadata.unknown())
+            .addConstNull()
+            .addConstInt(42)
+            .build();
 
     LIRIterator it = code.iterator();
 
@@ -43,23 +55,17 @@
     assertSame(it, next);
 
     it.accept(
-        (int opcode, int operandOffset, int operandSize) -> {
-          int headerSize = 1;
-          assertEquals(LIROpcodes.ACONST_NULL, opcode);
-          assertEquals(offset.get() + headerSize, operandOffset);
-          assertEquals(0, operandSize);
-          offset.increment(headerSize + operandSize);
+        insn -> {
+          assertEquals(LIROpcodes.ACONST_NULL, insn.getOpcode());
+          assertEquals(0, insn.getRemainingOperandSizeInBytes());
         });
 
     assertTrue(it.hasNext());
     it.next();
     it.accept(
-        (int opcode, int operandOffset, int operandSize) -> {
-          int headerSize = 2; // opcode + payload-size
-          assertEquals(LIROpcodes.ICONST, opcode);
-          assertEquals(offset.get() + headerSize, operandOffset);
-          assertEquals(4, operandSize);
-          offset.increment(headerSize + operandSize);
+        insn -> {
+          assertEquals(LIROpcodes.ICONST, insn.getOpcode());
+          assertEquals(4, insn.getRemainingOperandSizeInBytes());
         });
     assertFalse(it.hasNext());
 
@@ -69,10 +75,10 @@
     for (LIRInstructionView view : code) {
       if (oldView == null) {
         oldView = view;
-        view.accept((opcode, ignore1, ignore2) -> assertEquals(LIROpcodes.ACONST_NULL, opcode));
+        view.accept(insn -> assertEquals(LIROpcodes.ACONST_NULL, insn.getOpcode()));
       } else {
         assertSame(oldView, view);
-        view.accept((opcode, ignore1, ignore2) -> assertEquals(LIROpcodes.ICONST, opcode));
+        view.accept(insn -> assertEquals(LIROpcodes.ICONST, insn.getOpcode()));
       }
     }
   }
diff --git a/src/test/java/com/android/tools/r8/lightir/LIRRoundtripTest.java b/src/test/java/com/android/tools/r8/lightir/LIRRoundtripTest.java
new file mode 100644
index 0000000..a169bfc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/lightir/LIRRoundtripTest.java
@@ -0,0 +1,74 @@
+// 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.lightir;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LIRRoundtripTest extends TestBase {
+
+  static class TestClass {
+    public static void main(String[] args) {
+      System.out.println("Hello, world!");
+    }
+  }
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultRuntimes().build();
+  }
+
+  private final TestParameters parameters;
+
+  public LIRRoundtripTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClasses(TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  @Test
+  public void testRoundtrip() throws Exception {
+    testForD8(parameters.getBackend())
+        .release()
+        .setMinApi(AndroidApiLevel.B)
+        .addProgramClasses(TestClass.class)
+        .addOptionsModification(
+            o -> {
+              o.testing.forceIRForCfToCfDesugar = true;
+              o.testing.roundtripThroughLIR = true;
+            })
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  @Test
+  public void testRoundtripDebug() throws Exception {
+    testForD8(parameters.getBackend())
+        .debug()
+        .setMinApi(AndroidApiLevel.B)
+        .addProgramClasses(TestClass.class)
+        .addOptionsModification(
+            o -> {
+              o.testing.forceIRForCfToCfDesugar = true;
+              o.testing.roundtripThroughLIR = true;
+            })
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeMapVersionTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeMapVersionTest.java
index bbc6c01..c91527d 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeMapVersionTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeMapVersionTest.java
@@ -4,13 +4,14 @@
 
 package com.android.tools.r8.mappingcompose;
 
-import static com.android.tools.r8.mappingcompose.ComposeHelpers.doubleToSingleQuote;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.MappingComposeException;
 import com.android.tools.r8.naming.MappingComposer;
 import com.android.tools.r8.utils.StringUtils;
 import org.junit.Test;
@@ -31,18 +32,20 @@
 
   private static final String mappingFoo =
       StringUtils.unixLines(
-          "# { id: 'com.android.tools.r8.mapping', version: '1.0' }", "com.foo -> a:");
+          "# { id: 'com.android.tools.r8.mapping', version: '2.1' }", "com.foo -> a:");
   private static final String mappingBar =
-      StringUtils.unixLines("# { id: 'com.android.tools.r8.mapping', version: '2.0' }", "a -> b:");
-  private static final String mappingResult =
-      StringUtils.unixLines(
-          "# {'id':'com.android.tools.r8.mapping','version':'2.0'}", "com.foo -> b:");
+      StringUtils.unixLines("# { id: 'com.android.tools.r8.mapping', version: '2.2' }", "a -> b:");
 
   @Test
   public void testCompose() throws Exception {
     ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
     ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
-    String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
-    assertEquals(mappingResult, doubleToSingleQuote(composed));
+    MappingComposeException mappingComposeException =
+        assertThrows(
+            MappingComposeException.class,
+            () -> MappingComposer.compose(mappingForFoo, mappingForBar));
+    assertEquals(
+        "Composition of mapping files supported from map version 2.1.",
+        mappingComposeException.getMessage());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java
index 98f1cd5..8ba1011 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.mappingcompose;
 
 import static com.android.tools.r8.mappingcompose.ComposeHelpers.doubleToSingleQuote;
-import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -26,12 +26,12 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+    return getTestParameters().withNoneRuntime().build();
   }
 
   private static final String mappingFoo =
       StringUtils.unixLines(
-          "# { id: 'com.android.tools.r8.mapping', version: '2.0' }",
+          "# { id: 'com.android.tools.r8.mapping', version: '2.1' }",
           "outline.Class -> a:",
           "    1:2:int some.inlinee():75:76 -> a",
           "    1:2:int outline():0 -> a",
@@ -41,24 +41,27 @@
           "    5:5:int foo.bar.baz.outlineCaller(int):98:98 -> s",
           "    5:5:int outlineCaller(int):24 -> s",
           "    27:27:int outlineCaller(int):0:0 -> s",
-          "    # { 'id':'com.android.tools.r8.outlineCallsite', 'positions': { '1': 4, '2': 5 } }");
-  private static final String mappingBar =
+          "    # { 'id':'com.android.tools.r8.outlineCallsite', 'positions': { '1': 4, '2': 5 },"
+              + " 'outline':'La;a()I' }");
+  private static final String mappingBar = StringUtils.unixLines("a -> b:");
+  private static final String mappingBaz =
       StringUtils.unixLines(
-          "a -> b:",
+          "b -> c:",
           "    4:5:int a():1:2 -> m",
-          "x -> c:",
+          "x -> y:",
           "    8:9:int s(int):4:5 -> o",
           "    42:42:int s(int):27:27 -> o");
   private static final String mappingResult =
       StringUtils.unixLines(
-          "# {'id':'com.android.tools.r8.mapping','version':'2.0'}",
-          "outline.Callsite -> c:",
+          "# {'id':'com.android.tools.r8.mapping','version':'2.1'}",
+          "outline.Callsite -> y:",
           "    8:8:int outlineCaller(int):23 -> o",
           "    9:9:int foo.bar.baz.outlineCaller(int):98:98 -> o",
           "    9:9:int outlineCaller(int):24 -> o",
           "    42:42:int outlineCaller(int):0:0 -> o",
-          "    # { 'id':'com.android.tools.r8.outlineCallsite', 'positions': { '4': 8, '5': 9 } }",
-          "outline.Class -> b:",
+          "    #"
+              + " {'id':'com.android.tools.r8.outlineCallsite','positions':{'4':8,'5':9},'outline':'Lc;m()I'}",
+          "outline.Class -> c:",
           "    4:5:int some.inlinee():75:76 -> m",
           "    4:5:int outline():0 -> m",
           "    # {'id':'com.android.tools.r8.outline'}");
@@ -67,8 +70,8 @@
   public void testCompose() throws Exception {
     ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
     ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
-    String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
-    // TODO(b/242682464): Update this test when the link has been added to the mapping information.
-    assertNotEquals(mappingResult, doubleToSingleQuote(composed));
+    ClassNameMapper mappingForBaz = ClassNameMapper.mapperFromString(mappingBaz);
+    String composed = MappingComposer.compose(mappingForFoo, mappingForBar, mappingForBaz);
+    assertEquals(mappingResult, doubleToSingleQuote(composed));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposePreambleCommentTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposePreambleCommentTest.java
new file mode 100644
index 0000000..98a070d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposePreambleCommentTest.java
@@ -0,0 +1,71 @@
+// 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.mappingcompose;
+
+import static com.android.tools.r8.mappingcompose.ComposeHelpers.doubleToSingleQuote;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.MappingComposer;
+import com.android.tools.r8.utils.StringUtils;
+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 ComposePreambleCommentTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  private static final String mappingFoo =
+      StringUtils.unixLines(
+          "# This is a multi line ",
+          "# preamble, with custom information",
+          "# {'id':'com.android.tools.r8.mapping','version':'2.1'}",
+          "# foo bar",
+          "# {'id':'this is invalid json due to no comma' neededInfo:'foobar' }",
+          "com.A -> a:",
+          "# This is a comment that will be removed.",
+          "com.B -> c:");
+  private static final String mappingBar =
+      StringUtils.unixLines(
+          "# Additional multiline ",
+          "# second preamble, with custom information",
+          "# {'id':'bar',neededInfo:'barbaz'}",
+          "# {'id':'com.android.tools.r8.mapping','version':'2.1'}",
+          "a -> b:",
+          "# This is another comment that will be removed.",
+          "c -> d:");
+  private static final String mappingResult =
+      StringUtils.unixLines(
+          "# This is a multi line ",
+          "# preamble, with custom information",
+          "# foo bar",
+          "# {'id':'this is invalid json due to no comma' neededInfo:'foobar' }",
+          "# Additional multiline ",
+          "# second preamble, with custom information",
+          "# {'id':'bar',neededInfo:'barbaz'}",
+          "# {'id':'com.android.tools.r8.mapping','version':'2.1'}",
+          "com.A -> b:",
+          "com.B -> d:");
+
+  @Test
+  public void testCompose() throws Exception {
+    ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithPreamble(mappingFoo);
+    ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithPreamble(mappingBar);
+    String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
+    assertEquals(mappingResult, doubleToSingleQuote(composed));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeRewriteFrameTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeRewriteFrameTest.java
index cae1f89..f8b6ebc 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeRewriteFrameTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeRewriteFrameTest.java
@@ -31,7 +31,7 @@
 
   private static final String mappingFoo =
       StringUtils.unixLines(
-          "# { id: 'com.android.tools.r8.mapping', version: '2.0' }",
+          "# { id: 'com.android.tools.r8.mapping', version: '2.1' }",
           "my.CustomException -> a:",
           "foo.Bar -> x:",
           "    4:4:void other.Class.inlinee():23:23 -> a",
@@ -40,13 +40,13 @@
               + "conditions: ['throws(La;)'], actions: ['removeInnerFrames(1)'] }");
   private static final String mappingBar =
       StringUtils.unixLines(
-          "# { id: 'com.android.tools.r8.mapping', version: '2.0' }",
+          "# { id: 'com.android.tools.r8.mapping', version: '2.1' }",
           "a -> b:",
           "x -> c:",
           "    8:8:void a(Other.Class):4:4 -> m");
   private static final String mappingResult =
       StringUtils.unixLines(
-          "# {'id':'com.android.tools.r8.mapping','version':'2.0'}",
+          "# {'id':'com.android.tools.r8.mapping','version':'2.1'}",
           "foo.Bar -> c:",
           "    8:8:void other.Class.inlinee():23:23 -> m",
           "    8:8:void caller(other.Class):7 -> m",
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeSyntheticTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeSyntheticTest.java
index 20585e9..e91eed6 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeSyntheticTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeSyntheticTest.java
@@ -31,7 +31,7 @@
 
   private static final String mappingFoo =
       StringUtils.unixLines(
-          "# { id: 'com.android.tools.r8.mapping', version: '1.0' }",
+          "# { id: 'com.android.tools.r8.mapping', version: '2.1' }",
           "com.foo -> a:",
           "# { id: 'com.android.tools.r8.synthesized' }",
           "    int f -> a",
@@ -40,7 +40,7 @@
           "    # { id: 'com.android.tools.r8.synthesized' }");
   private static final String mappingBar =
       StringUtils.unixLines(
-          "# { id: 'com.android.tools.r8.mapping', version: '1.0' }",
+          "# { id: 'com.android.tools.r8.mapping', version: '2.1' }",
           "a -> b:",
           "    int a -> b",
           "com.bar -> c:",
@@ -49,7 +49,7 @@
           "    # { id: 'com.android.tools.r8.synthesized' }");
   private static final String mappingResult =
       StringUtils.unixLines(
-          "# {'id':'com.android.tools.r8.mapping','version':'1.0'}",
+          "# {'id':'com.android.tools.r8.mapping','version':'2.1'}",
           "com.bar -> c:",
           "# {'id':'com.android.tools.r8.synthesized'}",
           "    void bar() -> a",
diff --git a/src/test/java/com/android/tools/r8/naming/MapReaderVersionTest.java b/src/test/java/com/android/tools/r8/naming/MapReaderVersionTest.java
index 5a4db90..d1991df 100644
--- a/src/test/java/com/android/tools/r8/naming/MapReaderVersionTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MapReaderVersionTest.java
@@ -38,7 +38,8 @@
         CharSource.wrap(StringUtils.joinLines(lines)).openBufferedStream(),
         diagnosticsHandler,
         false,
-        true);
+        true,
+        false);
   }
 
   private static ClassNameMapper read(String... lines) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderArgumentsTest.java b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderArgumentsTest.java
index 6c7156d..8e91ae1 100644
--- a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderArgumentsTest.java
@@ -65,9 +65,6 @@
                     diagnosticMessage(containsString("Could not locate 'id'")),
                     diagnosticPosition(positionLine(4))),
                 allOf(
-                    diagnosticMessage(containsString("Not valid JSON")),
-                    diagnosticPosition(positionLine(6))),
-                allOf(
                     diagnosticMessage(containsString("Could not find a handler for bar")),
                     diagnosticPosition(positionLine(8)))))
         .assertAllInfosMatch(diagnosticType(MappingInformationDiagnostics.class));
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfRuleWithFieldAnnotation.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfRuleWithFieldAnnotation.java
new file mode 100644
index 0000000..3349571
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfRuleWithFieldAnnotation.java
@@ -0,0 +1,94 @@
+// 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.ifrule;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class IfRuleWithFieldAnnotation extends TestBase {
+
+  static final String EXPECTED = "foobar";
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public IfRuleWithFieldAnnotation(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Foo.class, Bar.class, SerializedName.class)
+        .addKeepMainRule(Foo.class)
+        .addKeepRules(
+            "-if class * {"
+                + " @com.android.tools.r8.shaking.ifrule.IfRuleWithFieldAnnotation$SerializedName"
+                + " <fields>; }\n"
+                + "-keep,allowobfuscation class <1> {\n"
+                + "  <init>(...);\n"
+                + "  @com.android.tools.r8.shaking.ifrule.IfRuleWithFieldAnnotation$SerializedNamed"
+                + " <fields>;\n"
+                + "}")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(Bar.class).field("int", "value"), isPresent());
+              assertThat(codeInspector.clazz(Bar.class).init("int"), isPresent());
+            })
+        .run(parameters.getRuntime(), Foo.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  public @interface SerializedName {}
+
+  public static class Foo {
+    public static Object object;
+
+    public static void main(String[] args) {
+      callOnBar(args);
+      System.out.println("foobar");
+    }
+
+    private static void callOnBar(String[] args) {
+      if (System.currentTimeMillis() == 0) {
+        int i = ((Bar) instantiateObject()).value;
+        System.out.println(i);
+      }
+    }
+
+    private static Object instantiateObject() {
+      try {
+        return Class.forName("class" + System.currentTimeMillis()).newInstance();
+      } catch (Exception e) {
+        e.printStackTrace();
+      }
+      return null;
+    }
+  }
+
+  public static class Bar {
+    @SerializedName public int value;
+
+    public Bar(int value) {
+      this.value = value;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/startup/InliningOutOfStartupPartitionTest.java b/src/test/java/com/android/tools/r8/startup/InliningOutOfStartupPartitionTest.java
new file mode 100644
index 0000000..6eff67b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/InliningOutOfStartupPartitionTest.java
@@ -0,0 +1,91 @@
+// 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.startup;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.experimental.startup.StartupClass;
+import com.android.tools.r8.experimental.startup.StartupItem;
+import com.android.tools.r8.experimental.startup.StartupMethod;
+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.utils.StartupTestingUtils;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 InliningOutOfStartupPartitionTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimesAndAllApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    List<StartupItem<ClassReference, MethodReference, ?>> startupItems =
+        ImmutableList.of(
+            StartupClass.referenceBuilder()
+                .setClassReference(Reference.classFromClass(Main.class))
+                .build(),
+            StartupMethod.referenceBuilder()
+                .setMethodReference(MethodReferenceUtils.mainMethod(Main.class))
+                .build());
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .apply(
+            testBuilder -> StartupTestingUtils.setStartupConfiguration(testBuilder, startupItems))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject mainClassSubject = inspector.clazz(Main.class);
+              assertThat(mainClassSubject, isPresent());
+              assertThat(mainClassSubject.uniqueMethodWithName("postStartupMethod"), isAbsent());
+
+              ClassSubject postStartupClassSubject = inspector.clazz(PostStartupClass.class);
+              // TODO(b/242815611): Should be present since inlining increases the size of the
+              //  startup.
+              assertThat(postStartupClassSubject, isAbsent());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      PostStartupClass.runPostStartup();
+    }
+
+    static void postStartupMethod() {
+      System.out.println("Hello, world!");
+    }
+  }
+
+  static class PostStartupClass {
+
+    static void runPostStartup() {
+      Main.postStartupMethod();
+    }
+  }
+}
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 ec7e59f..ad43216 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
@@ -10,11 +10,11 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.D8TestBuilder;
 import com.android.tools.r8.D8TestRunResult;
-import com.android.tools.r8.StartupProfileProvider;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.experimental.startup.StartupConfigurationParser;
 import com.android.tools.r8.experimental.startup.StartupItem;
 import com.android.tools.r8.experimental.startup.StartupProfile;
@@ -22,9 +22,12 @@
 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.origin.Origin;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.startup.StartupProfileBuilder;
+import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.ClassReferenceUtils;
 import com.android.tools.r8.utils.MethodReferenceUtils;
@@ -157,7 +160,23 @@
                                   builder.addStartupItem(
                                       convertStartupItemToDex(startupItem, dexItemFactory))))
                   .build();
-          StartupProfileProvider startupProfileProvider = startupProfile::serializeToString;
+          StartupProfileProvider startupProfileProvider =
+              new StartupProfileProvider() {
+                @Override
+                public String get() {
+                  return startupProfile.serializeToString();
+                }
+
+                @Override
+                public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) {
+                  throw new Unimplemented();
+                }
+
+                @Override
+                public Origin getOrigin() {
+                  return Origin.unknown();
+                }
+              };
           options.getStartupOptions().setStartupProfileProvider(startupProfileProvider);
         });
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index e29b4e8..aeb419e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -281,6 +281,10 @@
     return clazz(Reference.classFromTypeName(name));
   }
 
+  public ClassNameMapper getMapping() {
+    return mapping;
+  }
+
   // Simple wrapper to more easily change the implementation for retracing subjects.
   // This should in time be replaced by use of the Retrace API.
   public static class MappingWrapper {
diff --git a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1 b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
index d9e0fb6..040d696 100644
--- a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
+++ b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
@@ -1 +1 @@
-2b8f463bcc995898411329d9cd55892289d5763e
\ No newline at end of file
+98dc48c246bd0855133e138c2cd2b5835cf862cf
\ No newline at end of file
diff --git a/tools/apk_masseur.py b/tools/apk_masseur.py
index 7f3932b..2bcea93 100755
--- a/tools/apk_masseur.py
+++ b/tools/apk_masseur.py
@@ -75,7 +75,7 @@
   with utils.ChangedWorkingDirectory(processed_out, quiet=quiet):
     dex_files = glob.glob('*.dex')
     resource_files = glob.glob(resources) if resources else []
-    cmd = ['zip', '-u', '-9', processed_apk] + dex_files + resource_files
+    cmd = ['zip', '-u', '-0', processed_apk] + dex_files + resource_files
     utils.RunCmd(cmd, quiet=quiet, logging=logging)
   return processed_apk
 
diff --git a/tools/apk_utils.py b/tools/apk_utils.py
index 906284e..f126f91 100755
--- a/tools/apk_utils.py
+++ b/tools/apk_utils.py
@@ -56,7 +56,7 @@
   zipalign_path = (
       'zipalign' if 'build_tools' in os.environ.get('PATH')
       else os.path.join(utils.getAndroidBuildTools(), 'zipalign'))
-  cmd = [zipalign_path, '-f', '4', apk, aligned_apk]
+  cmd = [zipalign_path, '-f', '-p', '4', apk, aligned_apk]
   utils.RunCmd(cmd, quiet=True, logging=False)
   return aligned_apk
 
diff --git a/tools/archive.py b/tools/archive.py
index 652fbe9..6a39594 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -31,6 +31,9 @@
   result.add_option('--dry-run-output', '--dry_run_output',
       help='Output directory for \'build only, no upload\'.',
       type="string", action="store")
+  result.add_option('--skip-gradle-build', '--skip_gradle_build',
+      help='Skip Gradle build. Can only be used for local testing.',
+      default=False, action='store_true')
   return result.parse_args()
 
 def GetVersion():
@@ -112,6 +115,8 @@
        not os.path.isdir(options.dry_run_output))):
     raise Exception(options.dry_run_output
         + ' does not exist or is not a directory')
+  if (options.skip_gradle_build and not options.dry_run):
+    raise Exception('Using --skip-gradle-build only supported with --dry-run')
 
   if utils.is_bot() and not utils.IsWindows():
     SetRLimitToMax()
@@ -134,27 +139,35 @@
       version_writer.write('version-file.version.code=1\n')
 
     # Create maven release which uses a build that exclude dependencies.
-    create_maven_release.generate_r8_maven_zip(utils.MAVEN_ZIP, version_file=version_file)
     create_maven_release.generate_r8_maven_zip(
-        utils.MAVEN_ZIP_LIB, is_r8lib=True, version_file=version_file)
+        utils.MAVEN_ZIP,
+        version_file=version_file,
+        skip_gradle_build=options.skip_gradle_build)
+    create_maven_release.generate_r8_maven_zip(
+        utils.MAVEN_ZIP_LIB,
+        is_r8lib=True,
+        version_file=version_file,
+        skip_gradle_build=options.skip_gradle_build)
 
     # Generate and copy a full build without dependencies.
-    gradle.RunGradleExcludeDeps([utils.R8, utils.R8_SRC])
+    if (not options.skip_gradle_build):
+      gradle.RunGradleExcludeDeps([utils.R8, utils.R8_SRC])
     shutil.copyfile(utils.R8_JAR, utils.R8_FULL_EXCLUDE_DEPS_JAR)
 
     # Ensure all archived artifacts has been built before archiving.
     # The target tasks postfixed by 'lib' depend on the actual target task so
     # building it invokes the original task first.
     # The '-Pno_internal' flag is important because we generate the lib based on uses in tests.
-    gradle.RunGradle([
-        utils.R8,
-        utils.R8LIB,
-        utils.R8LIB_NO_DEPS,
-        utils.R8RETRACE,
-        utils.R8RETRACE_NO_DEPS,
-        utils.LIBRARY_DESUGAR_CONVERSIONS,
-        '-Pno_internal'
-    ])
+    if (not options.skip_gradle_build):
+      gradle.RunGradle([
+          utils.R8,
+          utils.R8LIB,
+          utils.R8LIB_NO_DEPS,
+          utils.R8RETRACE,
+          utils.R8RETRACE_NO_DEPS,
+          utils.LIBRARY_DESUGAR_CONVERSIONS,
+          '-Pno_internal'
+      ])
 
     # Create maven release of the desuage_jdk_libs configuration. This require
     # an r8.jar with dependencies to have been built.
@@ -169,6 +182,22 @@
         utils.DESUGAR_IMPLEMENTATION_JDK11,
         utils.LIBRARY_DESUGAR_CONVERSIONS_LEGACY_ZIP)
 
+    create_maven_release.generate_desugar_configuration_maven_zip(
+        utils.DESUGAR_CONFIGURATION_JDK11_MINIMAL_MAVEN_ZIP,
+        utils.DESUGAR_CONFIGURATION_JDK11_MINIMAL,
+        utils.DESUGAR_IMPLEMENTATION_JDK11,
+        utils.LIBRARY_DESUGAR_CONVERSIONS_ZIP)
+    create_maven_release.generate_desugar_configuration_maven_zip(
+        utils.DESUGAR_CONFIGURATION_JDK11_MAVEN_ZIP,
+        utils.DESUGAR_CONFIGURATION_JDK11,
+        utils.DESUGAR_IMPLEMENTATION_JDK11,
+        utils.LIBRARY_DESUGAR_CONVERSIONS_ZIP)
+    create_maven_release.generate_desugar_configuration_maven_zip(
+        utils.DESUGAR_CONFIGURATION_JDK11_NIO_MAVEN_ZIP,
+        utils.DESUGAR_CONFIGURATION_JDK11_NIO,
+        utils.DESUGAR_IMPLEMENTATION_JDK11,
+        utils.LIBRARY_DESUGAR_CONVERSIONS_ZIP)
+
     version = GetVersion()
     is_main = IsMain(version)
     if is_main:
@@ -200,6 +229,9 @@
       utils.DESUGAR_CONFIGURATION_MAVEN_ZIP,
       utils.DESUGAR_CONFIGURATION_JDK11_LEGACY,
       utils.DESUGAR_CONFIGURATION_JDK11_LEGACY_MAVEN_ZIP,
+      utils.DESUGAR_CONFIGURATION_JDK11_MINIMAL_MAVEN_ZIP,
+      utils.DESUGAR_CONFIGURATION_JDK11_MAVEN_ZIP,
+      utils.DESUGAR_CONFIGURATION_JDK11_NIO_MAVEN_ZIP,
       utils.GENERATED_LICENSE,
     ]:
       file_name = os.path.basename(file)
diff --git a/tools/archive_desugar_jdk_libs.py b/tools/archive_desugar_jdk_libs.py
index 5298435..1762c2c 100755
--- a/tools/archive_desugar_jdk_libs.py
+++ b/tools/archive_desugar_jdk_libs.py
@@ -19,7 +19,10 @@
 # repository to fetch the artifact com.android.tools:desugar_jdk_libs:1.0.0
 
 import archive
+import defines
 import git_utils
+import gradle
+import hashlib
 import jdk
 import optparse
 import os
@@ -28,17 +31,59 @@
 import subprocess
 import sys
 import utils
+import zipfile
 
 VERSION_FILE_JDK8 = 'VERSION.txt'
+VERSION_FILE_JDK11_LEGACY = 'VERSION_JDK11_LEGACY.txt'
+VERSION_FILE_JDK11_MINIMAL = 'VERSION_JDK11_MINIMAL.txt'
 VERSION_FILE_JDK11 = 'VERSION_JDK11.txt'
-LIBRARY_NAME = 'desugar_jdk_libs'
+VERSION_FILE_JDK11_NIO = 'VERSION_JDK11_NIO.txt'
+
+VERSION_MAP = {
+  'jdk8': VERSION_FILE_JDK8,
+  'jdk11_legacy': VERSION_FILE_JDK11_LEGACY,
+  'jdk11_minimal': VERSION_FILE_JDK11_MINIMAL,
+  'jdk11': VERSION_FILE_JDK11,
+  'jdk11_nio': VERSION_FILE_JDK11_NIO
+}
+
+GITHUB_REPRO = 'desugar_jdk_libs'
+
+BASE_LIBRARY_NAME = 'desugar_jdk_libs'
+
+LIBRARY_NAME_MAP = {
+  'jdk8': BASE_LIBRARY_NAME,
+  'jdk11_legacy': BASE_LIBRARY_NAME,
+  'jdk11_minimal': BASE_LIBRARY_NAME + '_minimal',
+  'jdk11': BASE_LIBRARY_NAME,
+  'jdk11_nio': BASE_LIBRARY_NAME + '_nio'
+}
+
+MAVEN_RELEASE_TARGET_MAP = {
+  'jdk8': 'maven_release',
+  'jdk11_legacy': 'maven_release_jdk11_legacy',
+  'jdk11_minimal': 'maven_release_jdk11_minimal',
+  'jdk11': 'maven_release_jdk11',
+  'jdk11_nio': 'maven_release_jdk11_nio'
+}
+
+MAVEN_RELEASE_ZIP = {
+  'jdk8': BASE_LIBRARY_NAME + '.zip',
+  'jdk11_legacy': BASE_LIBRARY_NAME + '_jdk11_legacy.zip',
+  'jdk11_minimal': BASE_LIBRARY_NAME + '_jdk11_minimal.zip',
+  'jdk11': BASE_LIBRARY_NAME + '_jdk11.zip',
+  'jdk11_nio': BASE_LIBRARY_NAME + '_jdk11_nio.zip'
+}
+
 
 def ParseOptions(argv):
   result = optparse.OptionParser()
   result.add_option('--variant',
-      help='.',
-      choices = ['jdk8', 'jdk11'],
-      default='jdk11')
+      help="Variant(s) to build",
+      metavar=('<variants(s)>'),
+      choices=['jdk8', 'jdk11_legacy', 'jdk11_minimal', 'jdk11', 'jdk11_nio'],
+      default=[],
+      action='append')
   result.add_option('--dry-run', '--dry_run',
       help='Running on bot, use third_party dependency.',
       default=False,
@@ -89,7 +134,7 @@
 def CloneDesugaredLibrary(github_account, checkout_dir):
   git_utils.GitClone(
     'https://github.com/'
-        + github_account + '/' + LIBRARY_NAME, checkout_dir)
+        + github_account + '/' + GITHUB_REPRO, checkout_dir)
 
 def GetJavaEnv():
   java_env = dict(os.environ, JAVA_HOME = jdk.GetJdk11Home())
@@ -98,9 +143,11 @@
   return java_env
 
 
-def BuildDesugaredLibrary(checkout_dir, variant):
-  if (variant != 'jdk8' and variant != 'jdk11'):
-    raise Exception('Variant ' + variant + 'is not supported')
+def BuildDesugaredLibrary(checkout_dir, variant, version = None):
+  if not variant in MAVEN_RELEASE_TARGET_MAP:
+    raise Exception('Variant ' + variant + ' is not supported')
+  if variant != 'jdk8' and variant != 'jdk11_legacy' and version is None:
+    raise Exception('Variant ' + variant + ' require version for undesugaring')
   with utils.ChangedWorkingDirectory(checkout_dir):
     bazel = os.path.join(utils.BAZEL_TOOL, 'lib', 'bazel', 'bin', 'bazel')
     cmd = [
@@ -109,7 +156,7 @@
         'build',
         '--spawn_strategy=local',
         '--verbose_failures',
-        'maven_release' + ('_jdk11' if variant == 'jdk11' else '')]
+        MAVEN_RELEASE_TARGET_MAP[variant]]
     utils.PrintCmd(cmd)
     subprocess.check_call(cmd, env=GetJavaEnv())
     cmd = [bazel, 'shutdown']
@@ -122,19 +169,136 @@
       library_jar = os.path.join(
           checkout_dir, 'bazel-bin', 'src', 'share', 'classes', 'java', 'libjava.jar')
     else:
+      # All JDK11 variants use the same library code.
       library_jar = os.path.join(
           checkout_dir, 'bazel-bin', 'jdk11', 'src', 'd8_java_base_selected_with_addon.jar')
     maven_zip = os.path.join(
       checkout_dir,
       'bazel-bin',
-      LIBRARY_NAME + ('_jdk11' if variant == 'jdk11' else '') +'.zip')
-    return (library_jar, maven_zip)
+      MAVEN_RELEASE_ZIP[variant])
 
+    if variant != 'jdk8' and variant != 'jdk11_legacy':
+      # The undesugaring is temporary...
+      undesugared_maven_zip = os.path.join(checkout_dir, 'undesugared_maven')
+      Undesugar(variant, maven_zip, version, undesugared_maven_zip)
+      undesugared_maven_zip = os.path.join(checkout_dir, 'undesugared_maven.zip')
+      return (library_jar, undesugared_maven_zip)
+    else:
+      return (library_jar, maven_zip)
+
+def hash_for(file, hash):
+  with open(file, 'rb') as f:
+    while True:
+      # Read chunks of 1MB
+      chunk = f.read(2 ** 20)
+      if not chunk:
+        break
+      hash.update(chunk)
+  return hash.hexdigest()
+
+def write_md5_for(file):
+  hexdigest = hash_for(file, hashlib.md5())
+  with (open(file + '.md5', 'w')) as file:
+    file.write(hexdigest)
+
+def write_sha1_for(file):
+  hexdigest = hash_for(file, hashlib.sha1())
+  with (open(file + '.sha1', 'w')) as file:
+    file.write(hexdigest)
+
+def Undesugar(variant, maven_zip, version, undesugared_maven_zip):
+  gradle.RunGradle(['testJar', 'repackageTestDeps'])
+  with utils.TempDir() as tmp:
+    with zipfile.ZipFile(maven_zip, 'r') as zip_ref:
+      zip_ref.extractall(tmp)
+    desugar_jdk_libs_jar = os.path.join(
+          tmp,
+          'com',
+          'android',
+          'tools',
+          LIBRARY_NAME_MAP[variant],
+          version,
+          '%s-%s.jar' % (LIBRARY_NAME_MAP[variant], version))
+    print(desugar_jdk_libs_jar)
+    undesugared_jar = os.path.join(tmp, 'undesugared.jar')
+    buildLibs = os.path.join(defines.REPO_ROOT, 'build', 'libs')
+    cmd = [jdk.GetJavaExecutable(),
+      '-cp',
+      '%s:%s:%s' % (os.path.join(buildLibs, 'r8_with_deps.jar'), os.path.join(buildLibs, 'r8tests.jar'), os.path.join(buildLibs, 'test_deps_all.jar')),
+      'com.android.tools.r8.desugar.desugaredlibrary.jdk11.DesugaredLibraryJDK11Undesugarer',
+      desugar_jdk_libs_jar,
+      undesugared_jar]
+    print(cmd)
+    try:
+      output = subprocess.check_output(cmd, stderr = subprocess.STDOUT).decode('utf-8')
+    except subprocess.CalledProcessError as e:
+      print(e)
+      print(e.output)
+      raise e
+    print(output)
+    # Copy the undesugared jar into place and update the checksums.
+    shutil.copyfile(undesugared_jar, desugar_jdk_libs_jar)
+    write_md5_for(desugar_jdk_libs_jar)
+    write_sha1_for(desugar_jdk_libs_jar)
+    shutil.make_archive(undesugared_maven_zip, 'zip', tmp)
+    print(undesugared_maven_zip)
+    output = subprocess.check_output(['ls', '-l', os.path.dirname(undesugared_maven_zip)], stderr = subprocess.STDOUT).decode('utf-8')
+    print(output)
 
 def MustBeExistingDirectory(path):
   if (not os.path.exists(path) or not os.path.isdir(path)):
     raise Exception(path + ' does not exist or is not a directory')
 
+def BuildAndUpload(options, variant):
+  if options.build_only:
+    with utils.TempDir() as checkout_dir:
+      CloneDesugaredLibrary(options.github_account, checkout_dir)
+      (library_jar, maven_zip) = BuildDesugaredLibrary(checkout_dir, variant)
+      shutil.copyfile(
+        library_jar,
+        os.path.join(options.build_only, os.path.basename(library_jar)))
+      shutil.copyfile(
+        maven_zip,
+        os.path.join(options.build_only, os.path.basename(maven_zip)))
+      return
+
+  # Only handling versioned desugar_jdk_libs.
+  is_main = False
+
+  with utils.TempDir() as checkout_dir:
+    CloneDesugaredLibrary(options.github_account, checkout_dir)
+    version = GetVersion(os.path.join(checkout_dir, VERSION_MAP[variant]))
+
+    destination = archive.GetVersionDestination(
+        'gs://', LIBRARY_NAME_MAP[variant] + '/' + version, is_main)
+    if utils.cloud_storage_exists(destination) and not options.dry_run:
+      raise Exception(
+          'Target archive directory %s already exists' % destination)
+
+    (library_jar, maven_zip) = BuildDesugaredLibrary(checkout_dir, variant, version)
+
+    storage_path = LIBRARY_NAME_MAP[variant] + '/' + version
+    # Upload the jar file with the library.
+    destination = archive.GetUploadDestination(
+        storage_path, LIBRARY_NAME_MAP[variant] + '.jar', is_main)
+    Upload(options, library_jar, storage_path, destination, is_main)
+
+    # Upload the maven zip file with the library.
+    destination = archive.GetUploadDestination(
+        storage_path, MAVEN_RELEASE_ZIP[variant], is_main)
+    Upload(options, maven_zip, storage_path, destination, is_main)
+
+    # Upload the jar file for accessing GCS as a maven repro.
+    maven_destination = archive.GetUploadDestination(
+        utils.get_maven_path('desugar_jdk_libs', version),
+        'desugar_jdk_libs-%s.jar' % version,
+        is_main)
+    if options.dry_run:
+      print('Dry run, not actually creating maven repo')
+    else:
+      utils.upload_file_to_cloud_storage(library_jar, maven_destination)
+      print('Maven repo root available at: %s' % archive.GetMavenUrl(is_main))
+
 def Main(argv):
   (options, args) = ParseOptions(argv)
   if (len(args) > 0):
@@ -154,58 +318,8 @@
   utils.DownloadFromGoogleCloudStorage(utils.JAVA8_SHA_FILE)
   utils.DownloadFromGoogleCloudStorage(utils.JAVA11_SHA_FILE)
 
-  if options.build_only:
-    with utils.TempDir() as checkout_dir:
-      CloneDesugaredLibrary(options.github_account, checkout_dir)
-      (library_jar, maven_zip) = BuildDesugaredLibrary(checkout_dir, options.variant)
-      shutil.copyfile(
-        library_jar,
-        os.path.join(options.build_only, os.path.basename(library_jar)))
-      shutil.copyfile(
-        maven_zip,
-        os.path.join(options.build_only, os.path.basename(maven_zip)))
-      return
-
-  # Only handling versioned desugar_jdk_libs.
-  is_main = False
-
-  with utils.TempDir() as checkout_dir:
-    CloneDesugaredLibrary(options.github_account, checkout_dir)
-    version = GetVersion(
-      os.path.join(
-        checkout_dir,
-        VERSION_FILE_JDK11 if options.variant == 'jdk11' else VERSION_FILE_JDK8))
-
-    destination = archive.GetVersionDestination(
-        'gs://', LIBRARY_NAME + '/' + version, is_main)
-    if utils.cloud_storage_exists(destination) and not options.dry_run:
-      raise Exception(
-          'Target archive directory %s already exists' % destination)
-
-    (library_jar, maven_zip) = BuildDesugaredLibrary(checkout_dir, options.variant)
-
-    storage_path = LIBRARY_NAME + '/' + version
-    # Upload the jar file with the library.
-    destination = archive.GetUploadDestination(
-        storage_path, LIBRARY_NAME + '.jar', is_main)
-    Upload(options, library_jar, storage_path, destination, is_main)
-
-    # Upload the maven zip file with the library.
-    destination = archive.GetUploadDestination(
-        storage_path, LIBRARY_NAME + '.zip', is_main)
-    Upload(options, maven_zip, storage_path, destination, is_main)
-
-    # Upload the jar file for accessing GCS as a maven repro.
-    maven_destination = archive.GetUploadDestination(
-        utils.get_maven_path('desugar_jdk_libs', version),
-        'desugar_jdk_libs-%s.jar' % version,
-        is_main)
-    if options.dry_run:
-      print('Dry run, not actually creating maven repo')
-    else:
-      utils.upload_file_to_cloud_storage(library_jar, maven_destination)
-      print('Maven repo root available at: %s' % archive.GetMavenUrl(is_main))
-
+  for v in options.variant:
+    BuildAndUpload(options, v)
 
 if __name__ == '__main__':
   sys.exit(Main(sys.argv[1:]))
diff --git a/tools/create_maven_release.py b/tools/create_maven_release.py
index 33e6f75..fc7eafb 100755
--- a/tools/create_maven_release.py
+++ b/tools/create_maven_release.py
@@ -81,7 +81,7 @@
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.android.tools</groupId>
-  <artifactId>desugar_jdk_libs_configuration</artifactId>
+  <artifactId>$artifactId</artifactId>
   <version>$version</version>
   <name>D8 configuration to desugar desugar_jdk_libs</name>
   <description>
@@ -260,11 +260,19 @@
   return result
 
 def write_default_r8_pom_file(pom_file, version):
-  write_pom_file(R8_POMTEMPLATE, pom_file, version, generate_dependencies(), '')
+  write_pom_file(R8_POMTEMPLATE, pom_file, version, dependencies=generate_dependencies())
 
-def write_pom_file(template, pom_file, version, dependencies='', library_licenses=''):
-  version_pom = template.substitute(
-      version=version, dependencies=dependencies, library_licenses=library_licenses)
+def write_pom_file(
+    template, pom_file, version, artifact_id=None, dependencies='', library_licenses=''):
+  version_pom = (
+      template.substitute(
+          artifactId=artifact_id,
+          version=version,
+          dependencies=dependencies,
+          library_licenses=library_licenses)
+    if artifact_id else
+      template.substitute(
+          version=version, dependencies=dependencies, library_licenses=library_licenses))
   with open(pom_file, 'w') as file:
     file.write(version_pom)
 
@@ -309,12 +317,13 @@
     base_no_zip = out[0:len(out)-4]
     make_archive(base_no_zip, 'zip', tmp_dir)
 
-def generate_r8_maven_zip(out, is_r8lib=False, version_file=None):
+def generate_r8_maven_zip(out, is_r8lib=False, version_file=None, skip_gradle_build=False):
   # Build the R8 no deps artifact.
-  if not is_r8lib:
-    gradle.RunGradleExcludeDeps([utils.R8])
-  else:
-    gradle.RunGradle([utils.R8LIB, '-Pno_internal'])
+  if not skip_gradle_build:
+    if not is_r8lib:
+      gradle.RunGradleExcludeDeps([utils.R8])
+    else:
+      gradle.RunGradle([utils.R8LIB, '-Pno_internal'])
 
   version = determine_version()
   with utils.TempDir() as tmp_dir:
@@ -331,8 +340,8 @@
         R8_POMTEMPLATE,
         pom_file,
         version,
-        "" if is_r8lib else generate_dependencies(),
-        generate_library_licenses() if is_r8lib else "")
+        dependencies='' if is_r8lib else generate_dependencies(),
+        library_licenses=generate_library_licenses() if is_r8lib else '')
     # Write the maven zip file.
     generate_maven_zip(
         'r8',
@@ -382,14 +391,31 @@
     make_archive(destination, 'zip', tmp_dir)
     move(destination + '.zip', destination)
 
+def convert_desugar_configuration(configuration, machine_configuration):
+  cmd = [jdk.GetJavaExecutable(),
+      '-cp',
+      utils.R8_JAR,
+      'com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.DesugaredLibraryConverter',
+      configuration,
+      utils.DESUGAR_IMPLEMENTATION_JDK11,
+      utils.get_android_jar(33),
+      machine_configuration]
+  subprocess.check_call(cmd)
+
 # Generate the maven zip for the configuration to desugar desugar_jdk_libs.
 def generate_desugar_configuration_maven_zip(
     out, configuration, implementation, conversions):
   with utils.TempDir() as tmp_dir:
-    version = utils.desugar_configuration_version(configuration)
+    (name, version) = utils.desugar_configuration_name_and_version(configuration, False)
+
+    if (not version.startswith("1.")):
+      machine_configuration = join(tmp_dir, "machine.json")
+      convert_desugar_configuration(configuration, machine_configuration)
+      configuration = machine_configuration
+
     # Generate the pom file.
     pom_file = join(tmp_dir, 'desugar_configuration.pom')
-    write_pom_file(DESUGAR_CONFIGUATION_POMTEMPLATE, pom_file, version)
+    write_pom_file(DESUGAR_CONFIGUATION_POMTEMPLATE, pom_file, version, artifact_id=name)
     # Generate the jar with the configuration file.
     jar_file = join(tmp_dir, 'desugar_configuration.jar')
     generate_jar_with_desugar_configuration(
@@ -398,8 +424,7 @@
         conversions,
         jar_file)
     # Write the maven zip file.
-    generate_maven_zip(
-        'desugar_jdk_libs_configuration', version, pom_file, jar_file, out)
+    generate_maven_zip(name, version, pom_file, jar_file, out)
 
 def main(argv):
   options = parse_options(argv)
diff --git a/tools/desugar_jdk_libs_repository.py b/tools/desugar_jdk_libs_repository.py
index 55db5b6..4417255 100755
--- a/tools/desugar_jdk_libs_repository.py
+++ b/tools/desugar_jdk_libs_repository.py
@@ -14,9 +14,12 @@
 import utils
 import create_maven_release
 
-class Configuration(Enum):
+class Variant(Enum):
     jdk8 = 'jdk8'
-    jdk11_legacy = 'jdk11-legacy'
+    jdk11_legacy = 'jdk11_legacy'
+    jdk11_minimal = 'jdk11_minimal'
+    jdk11 = 'jdk11'
+    jdk11_nio = 'jdk11_nio'
 
     def __str__(self):
         return self.value
@@ -32,7 +35,7 @@
                       default=False,
                       action='store_true',
                       help='Clear the Maven repository so it only has one version present')
-  parser.add_argument('--configuration', default='jdk8', type=Configuration, choices=list(Configuration))
+  parser.add_argument('--variant', type=Variant, choices=list(Variant))
   parser.add_argument('--desugar-jdk-libs-checkout', '--desugar_jdk_libs_checkout',
                       default=None,
                       metavar=('<path>'),
@@ -56,29 +59,70 @@
 def pom_file(unzip_dir, artifact, version):
   return jar_or_pom_file(unzip_dir, artifact, version, 'pom')
 
-def main():
-  args = parse_options()
-  if args.clear_repo:
-    shutil.rmtree(args.repo_root, ignore_errors=True)
-  utils.makedirs_if_needed(args.repo_root)
-  configuration = (utils.DESUGAR_CONFIGURATION
-                   if args.configuration is Configuration.jdk8
-                   else utils.DESUGAR_CONFIGURATION_JDK11_LEGACY)
-  implementation = (utils.DESUGAR_IMPLEMENTATION
-                    if args.configuration is Configuration.jdk8
-                    else utils.DESUGAR_IMPLEMENTATION_JDK11)
-  version_file = ('VERSION.txt'
-                  if args.configuration is Configuration.jdk8 else
-                  'VERSION_JDK11.txt')
-  with utils.TempDir() as tmp_dir:
-    version = utils.desugar_configuration_version(configuration)
+def run(args):
+  artifact = None
+  configuration_artifact = None
+  configuration = None
+  conversions = None
+  implementation = None
+  version_file = None
+  implementation_build_target = None
+  implementation_build_output = None
+  match args.variant:
+    case Variant.jdk8:
+      artifact = 'desugar_jdk_libs'
+      configuration_artifact = 'desugar_jdk_libs_configuration'
+      configuration = utils.DESUGAR_CONFIGURATION
+      conversions = utils.LIBRARY_DESUGAR_CONVERSIONS_LEGACY_ZIP
+      implementation = utils.DESUGAR_IMPLEMENTATION
+      version_file = 'VERSION.txt'
+      implementation_build_target = ':maven_release'
+      implementation_build_output = join('bazel-bin', 'desugar_jdk_libs.zip')
+    case Variant.jdk11_legacy:
+      artifact = 'desugar_jdk_libs'
+      configuration_artifact = 'desugar_jdk_libs_configuration'
+      configuration = utils.DESUGAR_CONFIGURATION_JDK11_LEGACY
+      conversions = utils.LIBRARY_DESUGAR_CONVERSIONS_LEGACY_ZIP
+      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')
+    case Variant.jdk11_minimal:
+      artifact = 'desugar_jdk_libs_minimal'
+      configuration_artifact = 'desugar_jdk_libs_configuration_minimal'
+      configuration = utils.DESUGAR_CONFIGURATION_JDK11_MINIMAL
+      conversions = utils.LIBRARY_DESUGAR_CONVERSIONS_ZIP
+      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')
+    case Variant.jdk11:
+      artifact = 'desugar_jdk_libs'
+      configuration_artifact = 'desugar_jdk_libs_configuration'
+      configuration = utils.DESUGAR_CONFIGURATION_JDK11
+      conversions = utils.LIBRARY_DESUGAR_CONVERSIONS_ZIP
+      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')
+    case Variant.jdk11_nio:
+      artifact = 'desugar_jdk_libs_nio'
+      configuration_artifact = 'desugar_jdk_libs_configuration_nio'
+      configuration = utils.DESUGAR_CONFIGURATION_JDK11_NIO
+      conversions = utils.LIBRARY_DESUGAR_CONVERSIONS_ZIP
+      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')
+  with utils.TempDir(delete=False) as tmp_dir:
+    (name, version) = utils.desugar_configuration_name_and_version(configuration, False)
     # 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):
-      utils.RunCmd(['git', 'clone', 'https://github.com/google/desugar_jdk_libs.git', checkout_dir])
+      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()
@@ -99,56 +143,51 @@
     create_maven_release.generate_desugar_configuration_maven_zip(
       maven_zip,
       configuration,
-      implementation)
+      implementation,
+      conversions)
     unzip_dir = join(tmp_dir, 'desugar_jdk_libs_configuration_unzipped')
     cmd = ['unzip', '-q', maven_zip, '-d', unzip_dir]
-    utils.RunCmd(cmd)
+    subprocess.check_call(cmd)
     cmd = [
       'mvn',
       'deploy:deploy-file',
       '-Durl=file:' + args.repo_root,
       '-DrepositoryId=someName',
-      '-Dfile=' + jar_file(unzip_dir, 'desugar_jdk_libs_configuration', version),
-      '-DpomFile=' + pom_file(unzip_dir, 'desugar_jdk_libs_configuration', version)]
-    utils.RunCmd(cmd)
+      '-Dfile=' + jar_file(unzip_dir, configuration_artifact, version),
+      '-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):
-      utils.RunCmd([
+      subprocess.check_call([
           'bazel',
           '--bazelrc=/dev/null',
           'build',
           '--spawn_strategy=local',
           '--verbose_failures',
-          (':maven_release'
-            if args.configuration is Configuration.jdk8
-          else ':maven_release_jdk11')])
+          implementation_build_target])
     unzip_dir = join(tmp_dir, 'desugar_jdk_libs_unzipped')
     cmd = [
         'unzip',
         '-q',
-        join(checkout_dir,
-            'bazel-bin',
-            ('desugar_jdk_libs.zip'
-              if args.configuration is Configuration.jdk8
-              else 'desugar_jdk_libs_jdk11.zip')),
+        join(checkout_dir, implementation_build_output),
         '-d',
         unzip_dir]
-    utils.RunCmd(cmd)
+    subprocess.check_call(cmd)
     cmd = [
       'mvn',
       'deploy:deploy-file',
       '-Durl=file:' + args.repo_root,
       '-DrepositoryId=someName',
-      '-Dfile=' + jar_file(unzip_dir, 'desugar_jdk_libs', version),
-      '-DpomFile=' + pom_file(unzip_dir, 'desugar_jdk_libs', version)]
-    utils.RunCmd(cmd)
+      '-Dfile=' + jar_file(unzip_dir, artifact, version),
+      '-DpomFile=' + pom_file(unzip_dir, artifact, version)]
+    subprocess.check_call(cmd)
 
     print()
     print("Artifacts:")
-    print("  com.android.tools:desugar_jdk_libs_configuration:" + version)
-    print("  com.android.tools:desugar_jdk_libs:" + version)
+    print("  com.android.tools:%s:%s" % (configuration_artifact, version))
+    print("  com.android.tools:%s:%s" % (artifact, version))
     print()
     print("deployed to Maven repository at " + args.repo_root + ".")
     print()
@@ -161,7 +200,7 @@
     print("to dependencyResolutionManagement.repositories in settings.gradle, and use")
     print('the "changing" property of the coreLibraryDesugaring dependency:')
     print()
-    print("  coreLibraryDesugaring('com.android.tools:desugar_jdk_libs:" +  version + "') {")
+    print("  coreLibraryDesugaring('com.android.tools:%s:%s') {" % (artifact, version))
     print("    changing = true")
     print("  }")
     print()
@@ -170,5 +209,17 @@
       + "to ensure the cache is not used when the same version is published."
       + "multiple times.")
 
+def main():
+  args = parse_options()
+  if args.clear_repo:
+    shutil.rmtree(args.repo_root, ignore_errors=True)
+  utils.makedirs_if_needed(args.repo_root)
+  if (args.variant):
+    run(args)
+  else:
+    for v in Variant:
+      args.variant = v
+      run(args)
+
 if __name__ == '__main__':
   sys.exit(main())
diff --git a/tools/linux/README.art-versions b/tools/linux/README.art-versions
index 94d78e1..5e38ebd 100644
--- a/tools/linux/README.art-versions
+++ b/tools/linux/README.art-versions
@@ -67,25 +67,29 @@
 
 art-13 (Android T)
 ------------------
-Build from tm-dev commit 442e1091f39417c692d91609af05e58af60d8e2b.
+Build branch android-13.0.0_r3.
 
-repo sync -cq -j24
+export BRANCH=android-13.0.0_r3
+mkdir ${BRANCH}
+cd ${BRANCH}
+repo init -u https://android.googlesource.com/platform/manifest -b ${BRANCH}
+repo sync -cq -j48
 source build/envsetup.sh
 lunch aosp_redfin-userdebug
-m -j48
-m -j48 build-art
-m -j48 test-art-host
+USE_RBE=false m -j48
+USE_RBE=false m -j48 build-art
+USE_RBE=false m -j48 test-art-host
 
 Collected into tools/linux/host/art-13. The "host" path element is checked
 by the script for running Art.
 
   cd <r8 checkout>
   scripts/update-host-art.sh \
-     --android-checkout <...>/android/tm-dev \
+     --android-checkout <...>/android-13.0.0_r3 \
      --art-dir host/art-13 \
      --android-product redfin
 
-(cd tools/linux/host; upload_to_google_storage.py -a --bucket r8-deps art-13)
+  (cd tools/linux/host; upload_to_google_storage.py -a --bucket r8-deps art-13)
 
 art-12.0.0 (Android S)
 ---------------------
diff --git a/tools/linux/host/art-13-dev.tar.gz.sha1 b/tools/linux/host/art-13-dev.tar.gz.sha1
deleted file mode 100644
index 5367655..0000000
--- a/tools/linux/host/art-13-dev.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-fed34a1eecaf012550cdd9df24434b8b2068a194
\ No newline at end of file
diff --git a/tools/linux/host/art-13.0.0.tar.gz.sha1 b/tools/linux/host/art-13.0.0.tar.gz.sha1
new file mode 100644
index 0000000..8ec439c
--- /dev/null
+++ b/tools/linux/host/art-13.0.0.tar.gz.sha1
@@ -0,0 +1 @@
+241ecc532b3bf804ef92bdb568ce7d4d85248434
\ No newline at end of file
diff --git a/tools/startup/adb_utils.py b/tools/startup/adb_utils.py
index 60a7ee3..a376018 100755
--- a/tools/startup/adb_utils.py
+++ b/tools/startup/adb_utils.py
@@ -150,6 +150,25 @@
         'Expected stdout to end with ".apk", was: %s' % stdout)
   return apk_path
 
+def get_component_name(app_id, activity):
+  if activity.startswith(app_id):
+    return '%s/.%s' % (app_id, activity[len(app_id)+1:])
+  else:
+    return '%s/%s' % (app_id, activity)
+
+def get_meminfo(app_id, device_id=None):
+  cmd = create_adb_cmd('shell dumpsys meminfo -s %s' % app_id, device_id)
+  stdout = subprocess.check_output(cmd).decode('utf-8').strip()
+  for line in stdout.splitlines():
+    if 'TOTAL PSS: ' in line:
+      elements = [s for s in line.replace('TOTAL ', 'TOTAL_').split()]
+      assert elements[0] == 'TOTAL_PSS:', elements[0]
+      assert elements[1].isdigit()
+      assert elements[2] == 'TOTAL_RSS:'
+      assert elements[3].isdigit()
+      return { 'total_pss': int(elements[1]), 'total_rss': int(elements[3]) }
+  raise ValueError('Unexpected stdout: %s' % stdout)
+
 def get_profile_data(app_id, device_id=None):
   with utils.TempDir() as temp:
     source = get_profile_path(app_id)
@@ -268,18 +287,22 @@
   time.sleep(sleep_in_seconds)
 
 def launch_activity(
-    app_id, activity, device_id=None, wait_for_activity_to_launch=False):
+    app_id,
+    activity,
+    device_id=None,
+    intent_data_uri=None,
+    wait_for_activity_to_launch=False):
   args = ['shell', 'am', 'start', '-n', '%s/%s' % (app_id, activity)]
+  if intent_data_uri:
+    args.extend(['-d', intent_data_uri])
   if wait_for_activity_to_launch:
     args.append('-W')
   cmd = create_adb_cmd(args, device_id)
   stdout = subprocess.check_output(cmd).decode('utf-8').strip()
-  if activity.startswith(app_id):
-    expected_stdout = (
-        'Starting: Intent { cmp=%s/.%s }' % (app_id, activity[len(app_id)+1:]))
-  else:
-    expected_stdout = 'Starting: Intent { cmp=%s/%s }' % (app_id, activity)
-  assert stdout.startswith(expected_stdout), 'was %s, expected %s' % (stdout, expected_stdout)
+  assert stdout.startswith('Starting: Intent {')
+  expected_component = 'cmp=%s' % get_component_name(app_id, activity)
+  assert expected_component in stdout, \
+      'was %s, expected %s' % (stdout, expected_component)
   lines = stdout.splitlines()
   result = {}
   for line in lines:
@@ -324,10 +347,12 @@
   stdout = subprocess.check_output(cmd).decode('utf-8').strip()
   assert len(stdout) == 0
 
-def start_logcat(device_id=None, format=None, filter=None):
+def start_logcat(device_id=None, format=None, filter=None, silent=False):
   args = ['logcat']
   if format:
     args.extend(['--format', format])
+  if silent:
+    args.append('-s')
   if filter:
     args.append(filter)
   cmd = create_adb_cmd(args, device_id)
diff --git a/tools/startup/measure_startup.py b/tools/startup/measure_startup.py
index e00797c..1b94fad 100755
--- a/tools/startup/measure_startup.py
+++ b/tools/startup/measure_startup.py
@@ -4,7 +4,9 @@
 # BSD-style license that can be found in the LICENSE file.
 
 import argparse
+import datetime
 import os
+import re
 import statistics
 import sys
 import time
@@ -141,6 +143,16 @@
 def run(out_dir, options, tmp_dir):
   assert adb_utils.get_screen_state(options.device_id).is_on_and_unlocked()
 
+  # Start logcat for time to fully drawn.
+  logcat_process = None
+  if options.fully_drawn_logcat_message:
+    adb_utils.clear_logcat(options.device_id)
+    logcat_process = adb_utils.start_logcat(
+        options.device_id,
+        format='time',
+        filter='%s ActivityTaskManager:I' % options.fully_drawn_logcat_filter,
+        silent=True)
+
   # Start perfetto trace collector.
   perfetto_process = None
   perfetto_trace_path = None
@@ -153,17 +165,64 @@
       options.app_id,
       options.main_activity,
       options.device_id,
+      intent_data_uri=options.intent_data_uri,
       wait_for_activity_to_launch=True)
 
+  # Wait for app to be fully drawn.
+  logcat = None
+  if logcat_process is not None:
+    wait_until_fully_drawn(logcat_process, options)
+    logcat = adb_utils.stop_logcat(logcat_process)
+
   # Wait for perfetto trace collector to stop.
   if options.perfetto:
     perfetto_utils.stop_record_android_trace(perfetto_process, out_dir)
 
   # Get minor and major page faults from app process.
-  data = compute_data(launch_activity_result, perfetto_trace_path, options)
+  data = compute_data(
+    launch_activity_result, logcat, perfetto_trace_path, options)
   write_data(out_dir, data)
   return data
 
+def wait_until_fully_drawn(logcat_process, options):
+  print('Waiting until app is fully drawn')
+  while True:
+    is_fully_drawn = any(
+        is_app_fully_drawn_logcat_message(line, options) \
+        for line in logcat_process.lines)
+    if is_fully_drawn:
+      break
+    time.sleep(1)
+  print('Done')
+
+def compute_time_to_fully_drawn_from_time_to_first_frame(logcat, options):
+  displayed_time = None
+  fully_drawn_time = None
+  for line in logcat:
+    if is_app_displayed_logcat_message(line, options):
+      displayed_time = get_timestamp_from_logcat_message(line)
+    elif is_app_fully_drawn_logcat_message(line, options):
+      fully_drawn_time = get_timestamp_from_logcat_message(line)
+  assert displayed_time is not None
+  assert fully_drawn_time is not None
+  assert fully_drawn_time > displayed_time
+  return fully_drawn_time - displayed_time
+
+def get_timestamp_from_logcat_message(line):
+  time_end_index = len('00-00 00:00:00.000')
+  time_format = '%m-%d %H:%M:%S.%f'
+  time_str = line[0:time_end_index] + '000'
+  time_seconds = datetime.datetime.strptime(time_str, time_format).timestamp()
+  return int(time_seconds * 1000)
+
+def is_app_displayed_logcat_message(line, options):
+  substring = 'Displayed %s' % adb_utils.get_component_name(
+      options.app_id, options.main_activity)
+  return substring in line
+
+def is_app_fully_drawn_logcat_message(line, options):
+  return re.search(options.fully_drawn_logcat_message, line)
+
 def add_data(data_total, data):
   for key, value in data.items():
     if key == 'app_id':
@@ -183,23 +242,35 @@
       assert isinstance(value, int), key
       data_total[key] = [value]
 
-def compute_data(launch_activity_result, perfetto_trace_path, options):
+def compute_data(launch_activity_result, logcat, perfetto_trace_path, options):
   minfl, majfl = adb_utils.get_minor_major_page_faults(
       options.app_id, options.device_id)
+  meminfo = adb_utils.get_meminfo(options.app_id, options.device_id)
   data = {
     'app_id': options.app_id,
     'time': time.ctime(time.time()),
     'minfl': minfl,
     'majfl': majfl
   }
+  data.update(meminfo)
   startup_data = compute_startup_data(
-      launch_activity_result, perfetto_trace_path, options)
+      launch_activity_result, logcat, perfetto_trace_path, options)
   return data | startup_data
 
-def compute_startup_data(launch_activity_result, perfetto_trace_path, options):
+def compute_startup_data(
+    launch_activity_result, logcat, perfetto_trace_path, options):
+  time_to_first_frame = launch_activity_result.get('total_time')
   startup_data = {
-    'adb_startup': launch_activity_result.get('total_time')
+    'adb_startup': time_to_first_frame
   }
+
+  # Time to fully drawn.
+  if options.fully_drawn_logcat_message:
+    startup_data['time_to_fully_drawn'] = \
+        compute_time_to_fully_drawn_from_time_to_first_frame(logcat, options) \
+            + time_to_first_frame
+
+  # Perfetto stats.
   perfetto_startup_data = {}
   if options.perfetto:
     trace_processor = TraceProcessor(file_path=perfetto_trace_path)
@@ -272,10 +343,19 @@
                       help='Device id (e.g., emulator-5554).')
   result.add_argument('--device-pin',
                       help='Device pin code (e.g., 1234)')
+  result.add_argument('--fully-drawn-logcat-filter',
+                      help='Logcat filter for the fully drawn message '
+                           '(e.g., "tag:I")')
+  result.add_argument('--fully-drawn-logcat-message',
+                      help='Logcat message that indicates that the app is '
+                           'fully drawn (regexp)')
   result.add_argument('--hot-startup',
                       help='Measure hot startup instead of cold startup',
                       default=False,
                       action='store_true')
+  result.add_argument('--intent-data-uri',
+                      help='Value to use for the -d argument to the intent '
+                           'that is used to launch the app')
   result.add_argument('--iterations',
                       help='Number of traces to generate',
                       default=1,
@@ -315,6 +395,10 @@
   # Profile is only used with --aot.
   assert options.aot or not options.baseline_profile
 
+  # Fully drawn logcat filter and message is absent or both present.
+  assert (options.fully_drawn_logcat_filter is None) == \
+      (options.fully_drawn_logcat_message is None)
+
   return options, args
 
 def global_setup(options):
diff --git a/tools/test.py b/tools/test.py
index 27c8b6a..5586802 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -260,7 +260,7 @@
       utils.DownloadFromGoogleCloudStorage(utils.BAZEL_SHA_FILE)
       utils.DownloadFromGoogleCloudStorage(utils.JAVA8_SHA_FILE)
       utils.DownloadFromGoogleCloudStorage(utils.JAVA11_SHA_FILE)
-      (library_jar, maven_zip) = archive_desugar_jdk_libs.BuildDesugaredLibrary(checkout_dir, 'jdk11' if options.desugared_library_configuration == 'jdk11' else 'jdk8')
+      (library_jar, maven_zip) = archive_desugar_jdk_libs.BuildDesugaredLibrary(checkout_dir, 'jdk11_legacy' if options.desugared_library_configuration == 'jdk11' else 'jdk8')
       desugar_jdk_libs = os.path.join(desugar_jdk_libs_dir, os.path.basename(library_jar))
       shutil.copyfile(library_jar, desugar_jdk_libs)
       print('Desugared library for test in ' + desugar_jdk_libs)
diff --git a/tools/utils.py b/tools/utils.py
index 9bd518f..1830f53 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -77,12 +77,24 @@
       'third_party', 'openjdk', 'desugar_jdk_libs', 'desugar_jdk_libs.jar')
 DESUGAR_CONFIGURATION_JDK11_LEGACY = os.path.join(
       'src', 'library_desugar', 'jdk11', 'desugar_jdk_libs_legacy.json')
+DESUGAR_CONFIGURATION_JDK11_MINIMAL = os.path.join(
+      'src', 'library_desugar', 'jdk11', 'desugar_jdk_libs_minimal.json')
+DESUGAR_CONFIGURATION_JDK11 = os.path.join(
+      'src', 'library_desugar', 'jdk11', 'desugar_jdk_libs.json')
+DESUGAR_CONFIGURATION_JDK11_NIO = os.path.join(
+      'src', 'library_desugar', 'jdk11', 'desugar_jdk_libs_nio.json')
 DESUGAR_IMPLEMENTATION_JDK11 = os.path.join(
       'third_party', 'openjdk', 'desugar_jdk_libs_11', 'desugar_jdk_libs.jar')
 DESUGAR_CONFIGURATION_MAVEN_ZIP = os.path.join(
   LIBS, 'desugar_jdk_libs_configuration.zip')
 DESUGAR_CONFIGURATION_JDK11_LEGACY_MAVEN_ZIP = os.path.join(
   LIBS, 'desugar_jdk_libs_configuration_jdk11_legacy.zip')
+DESUGAR_CONFIGURATION_JDK11_MINIMAL_MAVEN_ZIP = os.path.join(
+  LIBS, 'desugar_jdk_libs_configuration_jdk11_minimal.zip')
+DESUGAR_CONFIGURATION_JDK11_MAVEN_ZIP = os.path.join(
+  LIBS, 'desugar_jdk_libs_configuration_jdk11.zip')
+DESUGAR_CONFIGURATION_JDK11_NIO_MAVEN_ZIP = os.path.join(
+  LIBS, 'desugar_jdk_libs_configuration_jdk11_nio.zip')
 GENERATED_LICENSE = os.path.join(GENERATED_LICENSE_DIR, 'LICENSE')
 RT_JAR = os.path.join(REPO_ROOT, 'third_party/openjdk/openjdk-rt-1.8/rt.jar')
 R8LIB_KEEP_RULES = os.path.join(REPO_ROOT, 'src/main/keep.txt')
@@ -626,17 +638,48 @@
   # so we split on '('; clean up tailing spaces; and strip off 'R8 '.
   return output.split('(')[0].strip()[3:]
 
-def desugar_configuration_version(configuration):
+def desugar_configuration_name_and_version(configuration, is_for_maven):
+  name = 'desugar_jdk_libs_configuration'
   with open(configuration, 'r') as f:
     configuration_json = json.loads(f.read())
     configuration_format_version = \
         configuration_json.get('configuration_format_version')
+    if (not configuration_format_version):
+        raise Exception(
+            'No "configuration_format_version" found in ' + configuration)
+    if (configuration_format_version != 3
+        and configuration_format_version != 5
+        and configuration_format_version != (200 if is_for_maven else 100)):
+          raise Exception(
+              'Unsupported "configuration_format_version" "%s" found in %s'
+              % (configuration_format_version, configuration))
     version = configuration_json.get('version')
     if not version:
-      raise Exception(
-          'No "version" found in ' + configuration)
-    check_basic_semver_version(version, 'in ' + configuration, allowPrerelease = True)
-    return version
+      if configuration_format_version == (200 if is_for_maven else 100):
+        identifier = configuration_json.get('identifier')
+        if not identifier:
+          raise Exception(
+              'No "identifier" found in ' + configuration)
+        identifier_split = identifier.split(':')
+        if (len(identifier_split) != 3):
+          raise Exception('Invalid "identifier" found in ' + configuration)
+        if (identifier_split[0] != 'com.tools.android'):
+          raise Exception('Invalid "identifier" found in ' + configuration)
+        if not identifier_split[1].startswith('desugar_jdk_libs_configuration'):
+          raise Exception('Invalid "identifier" found in ' + configuration)
+        name = identifier_split[1]
+        version = identifier_split[2]
+      else:
+        raise Exception(
+            'No "version" found in ' + configuration)
+    else:
+      if configuration_format_version == (200 if is_for_maven else 100):
+        raise Exception(
+            'No "version" expected in ' + configuration)
+    # Disallow prerelease, as older R8 versions cannot parse it causing hard to
+    # understand errors.
+    check_basic_semver_version(version, 'in ' + configuration, allowPrerelease = False)
+    return (name, version)
 
 class SemanticVersion:
   def __init__(self, major, minor, patch, prerelease):