Merge commit '3295ee8ba91bb782b73433ea8f0859e834cf57a3' into dev-release
diff --git a/build.gradle b/build.gradle
index 90d75ef..db4592a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -349,7 +349,7 @@
                 "android_jar/lib-v31",
                 "android_jar/lib-v32",
                 "android_jar/lib-v33",
-		"android_jar/lib-master",
+                "android_jar/lib-master",
                 "api_database/api_database",
                 "api-outlining/simple-app-dump",
                 "binary_compatibility_tests/compiler_api_tests",
diff --git a/scripts/add-android-jar.sh b/scripts/add-android-jar.sh
index c83aa2e..eabdedc 100755
--- a/scripts/add-android-jar.sh
+++ b/scripts/add-android-jar.sh
@@ -16,8 +16,8 @@
 SDK_HOME=$HOME/Android/Sdk
 
 # Modify these to match the SDK android.jar to add.
-SDK_DIR_NAME=android-Sv2
-SDK_VERSION=32
+SDK_DIR_NAME=android-33
+SDK_VERSION=33
 
 SDK_DIR=$SDK_HOME/platforms/$SDK_DIR_NAME
 THIRD_PARTY_ANDROID_JAR=third_party/android_jar
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_human_comments.md b/src/library_desugar/jdk11/desugar_jdk_libs_human_comments.md
index 3fdcd72..9f79e04 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_human_comments.md
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_human_comments.md
@@ -1,20 +1,25 @@
 # Description of the human desugared library configuration file
 
-## Version
+## Top-level flags
+
+### Identifier
+
+The field `identifier` is the maven-coordinated id for the desugared library
+configuration file with the group id, the artifact id and the version number
+joined with colon.
+
+### Version
 
 The field `configuration_format_version` encodes a versioning number internal to
 R8/D8 in the form of an unsigned integer. It allows R8/D8 to know if the file
-given is supported. If the number if greater or equal to 100, the file is
-encoded using the human flags (by opposition to the legacy flags). Human flags
-are not shipped to external users. Human flags can be converted to machine flags
-which are shipped to external users. Users internal to Google are allowed to use
-directly human flags if we can easily update the file without backward
+given is supported. If the number is between 100 and 200, the file is encoded
+using the human flags (by opposition to the legacy and machine flags). Human
+flags are not shipped to external users. Human flags can be converted to machine
+flags which are shipped to external users. Users internal to Google are allowed
+to use directly human flags if we can easily update the file without backward
 compatibility issues.
 
-The field `identifier` is the maven-coordinated id for the desugared library
-configuration file.
-
-## Required compilation API level
+### Required compilation API level
 
 The field `required_compilation_api_level` encodes the minimal Android API level
 required for the desugared library to be compiled correctly. If the API of
@@ -22,13 +27,13 @@
 lower than this level, one has to upgrade the SDK version used to be able to use
 desugared libraries.
 
-## Synthesize prefix
+### Synthesize prefix
 
-The field `synthesized_library_classes_package_prefix` is used both to prefix
-type names of synthetic classes created during the L8 compilation and for some
-of the rewritings.
+The field `synthesized_library_classes_package_prefix` is used to prefix type
+names of synthetic classes created during the L8 compilation. It is also used
+for minification in the `java.` namespace to avoid collisions with platforms.
 
-## Library callbacks
+### Library callbacks
 
 The field `support_all_callbacks_from_library` is set if D8/R8 should generate
 extra callbacks, i.e., methods that may be called from specific library
@@ -51,10 +56,16 @@
 ### Flag rewrite_prefix
 
 `prefix: rewrittenPrefix`
-D8/R8 identifies any class type matching the prefix, and rewrite such types with
-the new prefix. Types not present as class types are not rewritten. Implicitly,
-all synthetic types derived from the matching type are also rewritten (lambdas
-and backports in the class, etc.).
+D8/R8 identifies any class which type matches the prefix, and rewrite such types
+with the new prefix. Types not present as class types are not rewritten.
+Implicitly, all synthetic types derived from the matching type are also
+rewritten (lambdas and backports in the class, etc.). Lastly, types referenced
+directly or indirectly from other flags (method retargeting, custom conversions,
+emulated interfaces, api generic type conversion) are identified and rewritten.
+
+The exact list of types is computed when generating the machine specification
+and the pattern matching never applies to user code, but instead to the
+desugared library jar and `android.jar`.
 
 Example:
 `foo.: f$.`
@@ -62,6 +73,31 @@
 foo.Foo -> f$.Foo. A type present foo.Bar, which is not the type of any class,
 will not generate any rewrite rule.
 
+### Flag maintain_prefix
+
+`prefix`
+D8/R8 identifies any class which type matches the prefix, and maintain such
+class in desugared library in the `java` namespace. Using this flag implicitly
+means that at runtime, either a class from the bootclasspath with the same name
+will take precedence and be used, or this class will be used on lower api
+levels.
+
+Using `maintain_prefix` allows desugared library code to work seamlessly with
+recent apis (no wrappers or conversions required), at the cost of preventing
+some signature changes (the signature in desugared library, even when shrinking,
+has to match the library one). This can be done only with classes which behavior
+is identical between desugared library and platform. Lastly, derived synthetic
+classes from code in such classes (lambdas, etc.) are still moved to the new
+namespace to avoid collisions with platform, so one has to be extra careful with
+package private access being broken in this set-up.
+
+Example:
+`foo.`
+A class present with the type `foo.Foo` will be maintained in the output as
+`foo.Foo`. If `synthesized_library_classes_package_prefix` is `f$`, then all the
+derived synthetic classes such as lambdas inside `foo.Foo` will be generated as
+`f$.Foo$lambda-hash`.
+
 ### Flag rewrite_derived_prefix
 
 `prefix: { fromPrefix: toPrefix }`
@@ -74,6 +110,56 @@
 A class present with the type foo.Foo will generate a rewrite rule:
 f$.Foo -> foo.Foo.
 
+### Flag dont_rewrite_prefix
+
+`prefix`
+D8/R8 identifies any class type matching the prefix, does not rewrite it and
+shrinks it away from the input. This flags takes precedence over 
+`rewrite_prefix` and `rewrite_derived_prefix`, allowing to disable prefix 
+rewriting on subpatterns.
+
+Example:
+`foo.`
+No class prefixed with foo. will be rewritten or kept in the output.
+
+### Flag never_outline_api
+
+`method`
+D8/R8 tries to outline api calls working only on high api levels and requiring
+conversions as much as possible to share the code and avoid soft verification
+errors. Methods specified here are never outlined. This is usually worse for the
+users, unless the api is expected to introspect the stack, in which case the
+extra frame is confusing.
+
+### Flag api_generic_types_conversion
+
+`methodApi: [i0, conversionMethod0, ..., iN, conversionMethodN]`
+D8/R8 automatically generates conversions surrounding api calls with rewritten
+types which are specified in custom conversions/wrappers. D8/R8 does not
+automatically convert types from generic types such as collection items, or
+conversion requiring to convert multiple values in general
+
+This flag is used to specify a plateform api (methodApi) which parameters or
+return value require a conversion different from the default one. The value is
+an array of pair. The pair's key (i0, .., iN) is the parameter index if greater
+or equal to 0, or the return type if -1. The pair's value (conversionMethod0,
+.., N) is the conversion method to use to convert the value.
+
+Example:
+`void bar(foo.Foo): [0, foo.Foo FooConverter#convertFoo(f$.Foo)]`
+When generating conversion for the api bar, the parameter 0 foo.Foo will be
+converted using FooConverter#convertFoo instead of the default conversion logic.
+
+### Flag retarget_static_field
+
+`field: retargetField`
+D8/R8 rewrites all references from the static field to the static retargetField.
+
+Example:
+`int Foo#bar: int Zorg#foo`
+D8/R8 rewrites all references to the field named bar in Foo to the field named
+foo in Zorg.
+
 ### Flag retarget_method
 
 `methodToRetarget: retargetType`
@@ -106,6 +192,13 @@
 the virtual dispatch is still valid and will correctly call the overrides if
 present.
 
+### Flag covariant_retarget_method
+
+`methodWithCovariantReturnType: alternativeReturnType`
+Any invoke to methodWithCovariantReturnType will be rewritten to a call to the
+same method with the alternativeReturnType and a checkcast. This is used to deal
+with covariant return types which are not present in desugared library.
+
 ### Flag amend_library_method
 
 `modifiers method`
@@ -117,6 +210,12 @@
 This flag amends the library to introduce the method, so resolution can find it
 and retarget it correctly.
 
+### Flag amend_library_field
+
+`modifiers field`
+Similar to amend_library_method, adds a field into the library so that field
+resolution can find it and it can be retargeted.
+
 ### Flag dont_retarget
 
 `type`
@@ -164,12 +263,13 @@
 Type convert(RewrittenType)
 RewrittenType convert(Type)
 
-## Extra keep rules
+## Shrinker config
 
-The last field is `extra_keep_rules`, it includes keep rules that are appended
-by L8 when shrinking the desugared library. It includes keep rules related to
+The last field is `shrinker_config`, it includes keep rules that are appended by
+L8 when shrinking the desugared library. It includes keep rules related to
 reflection inside the desugared library, related to enum to have EnumSet working
-and to keep the j$ prefix.
+and to keep the j$ prefix. It also includes various keep rules to suppor common
+serializers.
 
 ## Copyright
 
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index e8e5a04..02e06d1 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -58,6 +58,7 @@
   private final SourceFileProvider sourceFileProvider;
   private final boolean isAndroidPlatformBuild;
   private final List<StartupProfileProvider> startupProfileProviders;
+  private final ClassConflictResolver classConflictResolver;
 
   BaseCompilerCommand(boolean printHelp, boolean printVersion) {
     super(printHelp, printVersion);
@@ -78,6 +79,7 @@
     sourceFileProvider = null;
     isAndroidPlatformBuild = false;
     startupProfileProviders = null;
+    classConflictResolver = null;
   }
 
   BaseCompilerCommand(
@@ -98,7 +100,8 @@
       MapIdProvider mapIdProvider,
       SourceFileProvider sourceFileProvider,
       boolean isAndroidPlatformBuild,
-      List<StartupProfileProvider> startupProfileProviders) {
+      List<StartupProfileProvider> startupProfileProviders,
+      ClassConflictResolver classConflictResolver) {
     super(app);
     assert minApiLevel > 0;
     assert mode != null;
@@ -119,6 +122,7 @@
     this.sourceFileProvider = sourceFileProvider;
     this.isAndroidPlatformBuild = isAndroidPlatformBuild;
     this.startupProfileProviders = startupProfileProviders;
+    this.classConflictResolver = classConflictResolver;
   }
 
   /**
@@ -140,7 +144,8 @@
         .setMinApi(getMinApiLevel())
         .setOptimizeMultidexForLinearAlloc(isOptimizeMultidexForLinearAlloc())
         .setThreadCount(getThreadCount())
-        .setDesugarState(getDesugarState());
+        .setDesugarState(getDesugarState())
+        .setStartupProfileProviders(getStartupProfileProviders());
     if (getAndroidPlatformBuild()) {
       builder.setAndroidPlatformBuild(true);
     }
@@ -218,6 +223,10 @@
     return startupProfileProviders;
   }
 
+  ClassConflictResolver getClassConflictResolver() {
+    return classConflictResolver;
+  }
+
   DumpInputFlags getDumpInputFlags() {
     return dumpInputFlags;
   }
@@ -260,6 +269,7 @@
     private SourceFileProvider sourceFileProvider = null;
     private boolean isAndroidPlatformBuild = false;
     private List<StartupProfileProvider> startupProfileProviders = new ArrayList<>();
+    private ClassConflictResolver classConflictResolver = null;
 
     abstract CompilationMode defaultCompilationMode();
 
@@ -753,6 +763,9 @@
                   + " or earlier");
         }
       }
+      if (hasDesugaredLibraryConfiguration() && getAndroidPlatformBuild()) {
+        reporter.error("Android platform builds cannot use desugared library");
+      }
       super.validate();
     }
 
@@ -782,5 +795,21 @@
     List<Consumer<Inspector>> getOutputInspections() {
       return outputInspections;
     }
+
+    /**
+     * Set a conflict resolver to determine which class definition to use in case of duplicates.
+     *
+     * <p>If no resolver is set, the compiler will fail compilation in case of duplicates.
+     *
+     * @param resolver Resolver for choosing between duplicate classes.
+     */
+    public B setClassConflictResolver(ClassConflictResolver resolver) {
+      this.classConflictResolver = resolver;
+      return self();
+    }
+
+    ClassConflictResolver getClassConflictResolver() {
+      return classConflictResolver;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ClassConflictResolver.java b/src/main/java/com/android/tools/r8/ClassConflictResolver.java
new file mode 100644
index 0000000..8136f17
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ClassConflictResolver.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 com.android.tools.r8;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.ClassReference;
+import java.util.Collection;
+
+@Keep
+public interface ClassConflictResolver {
+
+  /**
+   * Callback called in case the compiler is provided with duplicate class definitions.
+   *
+   * <p>The callback may be called multiple times for the same type with different origins, or it
+   * may be called once with all origins. Assuming the client has provided unique origins for the
+   * various inputs, the number of origins in any call will be at least two.
+   *
+   * <p>Note that all the duplicates are in the program's compilation unit. In other words, none of
+   * them are classpath or library definitions.
+   *
+   * @param reference The type reference of the duplicated class.
+   * @param origins The multiple origins of the class.
+   * @param handler Diagnostics handler for reporting.
+   * @return Returns the origin to use or null to fail compilation.
+   */
+  Origin resolveDuplicateClass(
+      ClassReference reference, Collection<Origin> origins, DiagnosticsHandler handler);
+}
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 172bfa2..4eba3b9 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
+import com.android.tools.r8.utils.ProgramClassCollection;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -457,6 +458,7 @@
           enableMissingLibraryApiModeling,
           getAndroidPlatformBuild(),
           getStartupProfileProviders(),
+          getClassConflictResolver(),
           factory);
     }
   }
@@ -548,6 +550,7 @@
       boolean enableMissingLibraryApiModeling,
       boolean isAndroidPlatformBuild,
       List<StartupProfileProvider> startupProfileProviders,
+      ClassConflictResolver classConflictResolver,
       DexItemFactory factory) {
     super(
         inputApp,
@@ -567,7 +570,8 @@
         mapIdProvider,
         null,
         isAndroidPlatformBuild,
-        startupProfileProviders);
+        startupProfileProviders,
+        classConflictResolver);
     this.intermediate = intermediate;
     this.globalSyntheticsConsumer = globalSyntheticsConsumer;
     this.desugarGraphConsumer = desugarGraphConsumer;
@@ -690,10 +694,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.getStartupOptions().setStartupProfileProviders(getStartupProfileProviders());
+
+    internal.programClassConflictResolver =
+        ProgramClassCollection.wrappedConflictResolver(
+            getClassConflictResolver(), internal.reporter);
 
     internal.setDumpInputFlags(getDumpInputFlags());
     internal.dumpOptions = dumpOptions();
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index 4f5b985..c85fe0d 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -78,6 +78,7 @@
         .addAll(ParseFlagInfoImpl.getAssertionsFlags())
         .add(ParseFlagInfoImpl.getThreadCount())
         .add(ParseFlagInfoImpl.getMapDiagnostics())
+        .add(ParseFlagInfoImpl.getAndroidPlatformBuild())
         .add(ParseFlagInfoImpl.getVersion("d8"))
         .add(ParseFlagInfoImpl.getHelp())
         .build();
@@ -302,6 +303,8 @@
       } else if (arg.equals("--desugared-lib-pg-conf-output")) {
         StringConsumer consumer = new StringConsumer.FileConsumer(Paths.get(nextArg));
         builder.setDesugaredLibraryKeepRuleConsumer(consumer);
+      } else if (arg.equals("--android-platform-build")) {
+        builder.setAndroidPlatformBuild(true);
       } else if (arg.startsWith("--")) {
         if (tryParseAssertionArgument(builder, arg, origin)) {
           continue;
diff --git a/src/main/java/com/android/tools/r8/KeepMethodForCompileDump.java b/src/main/java/com/android/tools/r8/KeepMethodForCompileDump.java
new file mode 100644
index 0000000..b49f8d0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/KeepMethodForCompileDump.java
@@ -0,0 +1,7 @@
+// 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;
+
+@Keep
+public @interface KeepMethodForCompileDump {}
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 4069ece..7a2950d 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
 import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.ProgramClassCollection;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -99,6 +100,7 @@
       int threadCount,
       DumpInputFlags dumpInputFlags,
       MapIdProvider mapIdProvider,
+      ClassConflictResolver classConflictResolver,
       DexItemFactory factory) {
     super(
         inputApp,
@@ -118,7 +120,8 @@
         mapIdProvider,
         null,
         false,
-        null);
+        null,
+        classConflictResolver);
     this.d8Command = d8Command;
     this.r8Command = r8Command;
     this.desugaredLibrarySpecification = desugaredLibrarySpecification;
@@ -208,6 +211,10 @@
                 .build(),
             getAssertionsConfiguration());
 
+    internal.programClassConflictResolver =
+        ProgramClassCollection.wrappedConflictResolver(
+            getClassConflictResolver(), internal.reporter);
+
     if (!DETERMINISTIC_DEBUGGING) {
       assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
       internal.threadCount = getThreadCount();
@@ -425,6 +432,7 @@
           getThreadCount(),
           getDumpInputFlags(),
           getMapIdProvider(),
+          getClassConflictResolver(),
           factory);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ParseFlagInfoImpl.java b/src/main/java/com/android/tools/r8/ParseFlagInfoImpl.java
index ad88315..af8d407 100644
--- a/src/main/java/com/android/tools/r8/ParseFlagInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/ParseFlagInfoImpl.java
@@ -156,6 +156,13 @@
         "Note that fatal compiler errors cannot be mapped.");
   }
 
+  public static ParseFlagInfoImpl getAndroidPlatformBuild() {
+    return flag0(
+        "--android-platform-build",
+        "Compile as a platform build where the runtime/bootclasspath",
+        "is assumed to be the version specified by --min-api.");
+  }
+
   public static ParseFlagInfoImpl flag0(String flag, String... help) {
     return flag(flag, Collections.emptyList(), Arrays.asList(help));
   }
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 6adecce..cef341c 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -37,6 +37,7 @@
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
+import com.android.tools.r8.utils.ProgramClassCollection;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -661,7 +662,8 @@
               getSourceFileProvider(),
               enableMissingLibraryApiModeling,
               getAndroidPlatformBuild(),
-              getStartupProfileProviders());
+              getStartupProfileProviders(),
+              getClassConflictResolver());
 
       if (inputDependencyGraphConsumer != null) {
         inputDependencyGraphConsumer.finished();
@@ -848,7 +850,8 @@
       SourceFileProvider sourceFileProvider,
       boolean enableMissingLibraryApiModeling,
       boolean isAndroidPlatformBuild,
-      List<StartupProfileProvider> startupProfileProviders) {
+      List<StartupProfileProvider> startupProfileProviders,
+      ClassConflictResolver classConflictResolver) {
     super(
         inputApp,
         mode,
@@ -867,7 +870,8 @@
         mapIdProvider,
         sourceFileProvider,
         isAndroidPlatformBuild,
-        startupProfileProviders);
+        startupProfileProviders,
+        classConflictResolver);
     assert proguardConfiguration != null;
     assert mainDexKeepRules != null;
     this.mainDexKeepRules = mainDexKeepRules;
@@ -1067,10 +1071,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.getStartupOptions().setStartupProfileProviders(getStartupProfileProviders());
+
+    internal.programClassConflictResolver =
+        ProgramClassCollection.wrappedConflictResolver(
+            getClassConflictResolver(), internal.reporter);
 
     if (!DETERMINISTIC_DEBUGGING) {
       assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index 3b80233..9ffcdd2 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -103,6 +103,7 @@
                 "The <template> can reference the variables:",
                 "  %MAP_ID: map id (e.g., value of --map-id-template).",
                 "  %MAP_HASH: compiler generated mapping hash."))
+        .add(ParseFlagInfoImpl.getAndroidPlatformBuild())
         .add(ParseFlagInfoImpl.getVersion("r8"))
         .add(ParseFlagInfoImpl.getHelp())
         .build();
@@ -298,6 +299,8 @@
       } else if (arg.equals("--source-file-template")) {
         builder.setSourceFileProvider(
             SourceFileTemplateProvider.create(nextArg, builder.getReporter()));
+      } else if (arg.equals("--android-platform-build")) {
+        builder.setAndroidPlatformBuild(true);
       } else if (arg.startsWith("--")) {
         if (tryParseAssertionArgument(builder, arg, argsOrigin)) {
           continue;
diff --git a/src/main/java/com/android/tools/r8/StringResource.java b/src/main/java/com/android/tools/r8/StringResource.java
index 515f2ab..4d097e0 100644
--- a/src/main/java/com/android/tools/r8/StringResource.java
+++ b/src/main/java/com/android/tools/r8/StringResource.java
@@ -105,13 +105,5 @@
         throw new ResourceException(origin, e);
       }
     }
-
-    public String getStringWithRuntimeException() {
-      try {
-        return getString();
-      } catch (ResourceException e) {
-        throw new RuntimeException(e);
-      }
-    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/TextInputStream.java b/src/main/java/com/android/tools/r8/TextInputStream.java
new file mode 100644
index 0000000..433df09
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/TextInputStream.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8;
+
+import java.io.InputStream;
+import java.nio.charset.Charset;
+
+@Keep
+public interface TextInputStream {
+
+  InputStream getInputStream();
+
+  Charset getCharset();
+}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 4932691..3bd5968 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -47,6 +47,7 @@
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.naming.KotlinModuleSynthesizer;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapId;
 import com.android.tools.r8.origin.Origin;
@@ -224,7 +225,7 @@
       StartupOrder startupOrder =
           appView.appInfo().hasClassHierarchy()
               ? appView.appInfoWithClassHierarchy().getStartupOrder()
-              : StartupOrder.createInitialStartupOrder(options);
+              : StartupOrder.createInitialStartupOrderForD8(appView);
       distributor =
           new VirtualFile.FillFilesDistributor(
               this, classes, options, executorService, startupOrder);
@@ -605,13 +606,19 @@
       ExceptionUtils.withFinishedResourceHandler(options.reporter, options.mainDexListConsumer);
     }
 
+    KotlinModuleSynthesizer kotlinModuleSynthesizer = new KotlinModuleSynthesizer(appView);
+
     DataResourceConsumer dataResourceConsumer = options.dataResourceConsumer;
     if (dataResourceConsumer != null) {
       ImmutableList<DataResourceProvider> dataResourceProviders =
           appView.app().dataResourceProviders;
       ResourceAdapter resourceAdapter = new ResourceAdapter(appView);
       adaptAndPassDataResources(
-          options, dataResourceConsumer, dataResourceProviders, resourceAdapter);
+          options,
+          dataResourceConsumer,
+          dataResourceProviders,
+          resourceAdapter,
+          kotlinModuleSynthesizer);
 
       // Write the META-INF/services resources. Sort on service names and keep the order from
       // the input for the implementation lines for deterministic output.
@@ -638,6 +645,10 @@
                       options.reporter);
                 });
       }
+      // Rewrite/synthesize kotlin_module files
+      kotlinModuleSynthesizer
+          .synthesizeKotlinModuleFiles()
+          .forEach(file -> dataResourceConsumer.accept(file, options.reporter));
     }
 
     if (options.featureSplitConfiguration != null) {
@@ -645,7 +656,11 @@
           options.featureSplitConfiguration.getDataResourceProvidersAndConsumers()) {
         ResourceAdapter resourceAdapter = new ResourceAdapter(appView);
         adaptAndPassDataResources(
-            options, entry.getConsumer(), entry.getProviders(), resourceAdapter);
+            options,
+            entry.getConsumer(),
+            entry.getProviders(),
+            resourceAdapter,
+            kotlinModuleSynthesizer);
       }
     }
   }
@@ -654,7 +669,8 @@
       InternalOptions options,
       DataResourceConsumer dataResourceConsumer,
       Collection<DataResourceProvider> dataResourceProviders,
-      ResourceAdapter resourceAdapter) {
+      ResourceAdapter resourceAdapter,
+      KotlinModuleSynthesizer kotlinModuleSynthesizer) {
     Set<String> generatedResourceNames = new HashSet<>();
 
     for (DataResourceProvider dataResourceProvider : dataResourceProviders) {
@@ -676,7 +692,10 @@
                   // META-INF/services resources are handled below.
                   return;
                 }
-
+                if (kotlinModuleSynthesizer.isKotlinModuleFile(file)) {
+                  // .kotlin_module files are synthesized.
+                  return;
+                }
                 DataEntryResource adapted = resourceAdapter.adaptIfNeeded(file);
                 if (generatedResourceNames.add(adapted.getName())) {
                   dataResourceConsumer.accept(adapted, options.reporter);
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java
index 8d57a1c..f2b1e15 100644
--- a/src/main/java/com/android/tools/r8/dex/Marker.java
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -32,6 +32,7 @@
   public static final String PG_MAP_ID = "pg-map-id";
   public static final String R8_MODE = "r8-mode";
   private static final String NO_LIBRARY_DESUGARING = "<no-library-desugaring>";
+  private static final String ANDROID_PLATFORM_BUILD = "platform";
 
   public enum Tool {
     D8,
@@ -268,6 +269,17 @@
     return this;
   }
 
+  public boolean isAndroidPlatformBuild() {
+    return jsonObject.has(ANDROID_PLATFORM_BUILD)
+        && jsonObject.get(ANDROID_PLATFORM_BUILD).getAsBoolean();
+  }
+
+  public Marker setAndroidPlatformBuild() {
+    assert !jsonObject.has(ANDROID_PLATFORM_BUILD);
+    jsonObject.addProperty(ANDROID_PLATFORM_BUILD, true);
+    return this;
+  }
+
   @Override
   public String toString() {
     // In order to make printing of markers deterministic we sort the entries by key.
diff --git a/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java b/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
index b297a2b..0833358 100644
--- a/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
+++ b/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
@@ -5,10 +5,10 @@
 package com.android.tools.r8.dex;
 
 import com.android.tools.r8.dex.FileWriter.MixedSectionOffsets;
-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.experimental.startup.StartupOrder;
+import com.android.tools.r8.experimental.startup.profile.StartupClass;
+import com.android.tools.r8.experimental.startup.profile.StartupItem;
+import com.android.tools.r8.experimental.startup.profile.StartupMethod;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationDirectory;
@@ -82,21 +82,23 @@
             virtualFile.classes().size());
     LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(appView, true);
     StartupIndexedItemCollection indexedItemCollection = new StartupIndexedItemCollection();
-    for (StartupItem<DexType, DexMethod, ?> startupItem : startupOrderForWriting.getItems()) {
-      // All synthetic startup items should be removed after calling
-      // StartupOrder#toStartupOrderForWriting.
-      assert !startupItem.isSynthetic();
+    for (StartupItem startupItem : startupOrderForWriting.getItems()) {
       startupItem.accept(
           startupClass ->
               collectStartupItems(startupClass, indexedItemCollection, virtualFileDefinitions),
           startupMethod ->
               collectStartupItems(
-                  startupMethod, indexedItemCollection, virtualFileDefinitions, rewriter));
+                  startupMethod, indexedItemCollection, virtualFileDefinitions, rewriter),
+          syntheticStartupMethod -> {
+            // All synthetic startup items should be removed after calling
+            // StartupOrder#toStartupOrderForWriting.
+            assert false;
+          });
     }
   }
 
   private void collectStartupItems(
-      StartupClass<DexType, DexMethod> startupClass,
+      StartupClass startupClass,
       StartupIndexedItemCollection indexedItemCollection,
       Map<DexType, DexProgramClass> virtualFileDefinitions) {
     DexProgramClass definition = virtualFileDefinitions.get(startupClass.getReference());
@@ -119,7 +121,7 @@
   }
 
   private void collectStartupItems(
-      StartupMethod<DexType, DexMethod> startupMethod,
+      StartupMethod startupMethod,
       StartupIndexedItemCollection indexedItemCollection,
       Map<DexType, DexProgramClass> virtualFileDefinitions,
       LensCodeRewriterUtils rewriter) {
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index fafe700..f6b057a 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -1396,7 +1396,7 @@
         return;
       }
 
-      assert options.getStartupOptions().hasStartupProfileProvider();
+      assert options.getStartupOptions().hasStartupProfileProviders();
 
       // In practice, all startup classes should fit in a single dex file, so optimistically try to
       // commit the startup classes using a single transaction.
diff --git a/src/main/java/com/android/tools/r8/dump/DumpOptions.java b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
index 1d55987..27dddcc 100644
--- a/src/main/java/com/android/tools/r8/dump/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
@@ -10,9 +10,11 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.ThreadUtils;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -62,6 +64,7 @@
   private final FeatureSplitConfiguration featureSplitConfiguration;
   private final ProguardConfiguration proguardConfiguration;
   private final List<ProguardConfigurationRule> mainDexKeepRules;
+  private final Collection<StartupProfileProvider> startupProfileProviders;
   private final boolean enableMissingLibraryApiModeling;
   private final boolean isAndroidPlatformBuild;
 
@@ -86,6 +89,7 @@
       FeatureSplitConfiguration featureSplitConfiguration,
       ProguardConfiguration proguardConfiguration,
       List<ProguardConfigurationRule> mainDexKeepRules,
+      Collection<StartupProfileProvider> startupProfileProviders,
       boolean enableMissingLibraryApiModeling,
       boolean isAndroidPlatformBuild,
       Map<String, String> systemProperties,
@@ -105,6 +109,7 @@
     this.featureSplitConfiguration = featureSplitConfiguration;
     this.proguardConfiguration = proguardConfiguration;
     this.mainDexKeepRules = mainDexKeepRules;
+    this.startupProfileProviders = startupProfileProviders;
     this.enableMissingLibraryApiModeling = enableMissingLibraryApiModeling;
     this.isAndroidPlatformBuild = isAndroidPlatformBuild;
     this.systemProperties = systemProperties;
@@ -268,6 +273,14 @@
     return mainDexKeepRules;
   }
 
+  public boolean hasStartupProfileProviders() {
+    return startupProfileProviders != null && !startupProfileProviders.isEmpty();
+  }
+
+  public Collection<StartupProfileProvider> getStartupProfileProviders() {
+    return startupProfileProviders;
+  }
+
   public boolean dumpInputToFile() {
     return dumpInputToFile;
   }
@@ -293,6 +306,7 @@
     private FeatureSplitConfiguration featureSplitConfiguration;
     private ProguardConfiguration proguardConfiguration;
     private List<ProguardConfigurationRule> mainDexKeepRules;
+    private Collection<StartupProfileProvider> startupProfileProviders;
 
     private boolean enableMissingLibraryApiModeling = false;
     private boolean isAndroidPlatformBuild = false;
@@ -386,6 +400,12 @@
       return this;
     }
 
+    public Builder setStartupProfileProviders(
+        Collection<StartupProfileProvider> startupProfileProviders) {
+      this.startupProfileProviders = startupProfileProviders;
+      return this;
+    }
+
     public Builder setEnableMissingLibraryApiModeling(boolean value) {
       enableMissingLibraryApiModeling = value;
       return this;
@@ -432,6 +452,7 @@
           featureSplitConfiguration,
           proguardConfiguration,
           mainDexKeepRules,
+          startupProfileProviders,
           enableMissingLibraryApiModeling,
           isAndroidPlatformBuild,
           systemProperties,
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupOrder.java b/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupOrder.java
index d777790..032e0b8 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupOrder.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupOrder.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.experimental.startup;
 
+import com.android.tools.r8.experimental.startup.profile.StartupItem;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -18,12 +19,17 @@
   EmptyStartupOrder() {}
 
   @Override
+  public boolean contains(DexMethod method, SyntheticItems syntheticItems) {
+    return false;
+  }
+
+  @Override
   public boolean contains(DexType type, SyntheticItems syntheticItems) {
     return false;
   }
 
   @Override
-  public Collection<StartupItem<DexType, DexMethod, ?>> getItems() {
+  public Collection<StartupItem> getItems() {
     return Collections.emptyList();
   }
 
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/NonEmptyStartupOrder.java b/src/main/java/com/android/tools/r8/experimental/startup/NonEmptyStartupOrder.java
index 954fcba..25188c0 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/NonEmptyStartupOrder.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/NonEmptyStartupOrder.java
@@ -4,11 +4,14 @@
 
 package com.android.tools.r8.experimental.startup;
 
+import com.android.tools.r8.experimental.startup.profile.StartupClass;
+import com.android.tools.r8.experimental.startup.profile.StartupItem;
+import com.android.tools.r8.experimental.startup.profile.StartupMethod;
+import com.android.tools.r8.experimental.startup.profile.SyntheticStartupMethod;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.PrunedItems;
@@ -25,28 +28,41 @@
 
 public class NonEmptyStartupOrder extends StartupOrder {
 
-  private final LinkedHashSet<StartupItem<DexType, DexMethod, ?>> startupItems;
+  private final LinkedHashSet<StartupItem> startupItems;
 
   // Sets to allow efficient querying without boxing.
   private final Set<DexType> nonSyntheticStartupClasses = Sets.newIdentityHashSet();
   private final Set<DexType> syntheticStartupClasses = Sets.newIdentityHashSet();
 
-  NonEmptyStartupOrder(LinkedHashSet<StartupItem<DexType, DexMethod, ?>> startupItems) {
+  private final Set<DexMethod> nonSyntheticStartupMethods = Sets.newIdentityHashSet();
+
+  NonEmptyStartupOrder(LinkedHashSet<StartupItem> startupItems) {
     assert !startupItems.isEmpty();
     this.startupItems = startupItems;
-    for (StartupItem<DexType, DexMethod, ?> startupItem : startupItems) {
-      if (startupItem.isSynthetic()) {
-        assert startupItem.isStartupClass();
-        syntheticStartupClasses.add(startupItem.asStartupClass().getReference());
-      } else {
-        DexReference reference =
-            startupItem.apply(StartupClass::getReference, StartupMethod::getReference);
-        nonSyntheticStartupClasses.add(reference.getContextType());
-      }
+    for (StartupItem startupItem : startupItems) {
+      startupItem.accept(
+          startupClass -> nonSyntheticStartupClasses.add(startupClass.getReference()),
+          startupMethod -> {
+            nonSyntheticStartupClasses.add(startupMethod.getReference().getHolderType());
+            nonSyntheticStartupMethods.add(startupMethod.getReference());
+          },
+          syntheticStartupMethod ->
+              syntheticStartupClasses.add(syntheticStartupMethod.getSyntheticContextType()));
     }
   }
 
   @Override
+  public boolean contains(DexMethod method, SyntheticItems syntheticItems) {
+    if (nonSyntheticStartupMethods.contains(method)) {
+      return true;
+    }
+    if (syntheticItems.isSyntheticClass(method.getHolderType())) {
+      return containsSyntheticClass(method.getHolderType(), syntheticItems);
+    }
+    return false;
+  }
+
+  @Override
   public boolean contains(DexType type, SyntheticItems syntheticItems) {
     return syntheticItems.isSyntheticClass(type)
         ? containsSyntheticClass(type, syntheticItems)
@@ -69,7 +85,7 @@
   }
 
   @Override
-  public Collection<StartupItem<DexType, DexMethod, ?>> getItems() {
+  public Collection<StartupItem> getItems() {
     return startupItems;
   }
 
@@ -80,27 +96,28 @@
 
   @Override
   public StartupOrder rewrittenWithLens(GraphLens graphLens) {
-    LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems =
-        new LinkedHashSet<>(startupItems.size());
-    for (StartupItem<DexType, DexMethod, ?> startupItem : startupItems) {
-      if (startupItem.isStartupClass()) {
-        StartupClass<DexType, DexMethod> startupClass = startupItem.asStartupClass();
-        rewrittenStartupItems.add(
-            StartupClass.dexBuilder()
-                .setClassReference(graphLens.lookupType(startupClass.getReference()))
-                .setSynthetic(startupItem.isSynthetic())
-                .build());
-      } else {
-        assert !startupItem.isSynthetic();
-        StartupMethod<DexType, DexMethod> startupMethod = startupItem.asStartupMethod();
-        // TODO(b/238173796): This should account for one-to-many mappings. e.g., when a bridge is
-        //  created.
-        rewrittenStartupItems.add(
-            StartupMethod.dexBuilder()
-                .setMethodReference(
-                    graphLens.getRenamedMethodSignature(startupMethod.getReference()))
-                .build());
-      }
+    LinkedHashSet<StartupItem> rewrittenStartupItems = new LinkedHashSet<>(startupItems.size());
+    for (StartupItem startupItem : startupItems) {
+      // TODO(b/238173796): This should account for one-to-many mappings. e.g., when a bridge is
+      //  created.
+      startupItem.apply(
+          startupClass ->
+              rewrittenStartupItems.add(
+                  StartupClass.builder()
+                      .setClassReference(graphLens.lookupType(startupClass.getReference()))
+                      .build()),
+          startupMethod ->
+              rewrittenStartupItems.add(
+                  StartupMethod.builder()
+                      .setMethodReference(
+                          graphLens.getRenamedMethodSignature(startupMethod.getReference()))
+                      .build()),
+          syntheticStartupMethod ->
+              rewrittenStartupItems.add(
+                  SyntheticStartupMethod.builder()
+                      .setSyntheticContextReference(
+                          graphLens.lookupType(syntheticStartupMethod.getSyntheticContextType()))
+                      .build()));
     }
     return createNonEmpty(rewrittenStartupItems);
   }
@@ -126,29 +143,27 @@
    */
   @Override
   public StartupOrder toStartupOrderForWriting(AppView<?> appView) {
-    LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems =
-        new LinkedHashSet<>(startupItems.size());
+    LinkedHashSet<StartupItem> rewrittenStartupItems = new LinkedHashSet<>(startupItems.size());
     Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses =
         appView.getSyntheticItems().computeSyntheticContextsToSyntheticClasses(appView);
-    for (StartupItem<DexType, DexMethod, ?> startupItem : startupItems) {
+    for (StartupItem startupItem : startupItems) {
       addStartupItem(
           startupItem, rewrittenStartupItems, syntheticContextsToSyntheticClasses, appView);
     }
-    assert rewrittenStartupItems.stream().noneMatch(StartupItem::isSynthetic);
+    assert rewrittenStartupItems.stream().noneMatch(StartupItem::isSyntheticStartupMethod);
     return createNonEmpty(rewrittenStartupItems);
   }
 
   private static void addStartupItem(
-      StartupItem<DexType, DexMethod, ?> startupItem,
-      LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems,
+      StartupItem startupItem,
+      LinkedHashSet<StartupItem> rewrittenStartupItems,
       Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses,
       AppView<?> appView) {
-    if (startupItem.isSynthetic()) {
-      assert startupItem.isStartupClass();
-      StartupClass<DexType, DexMethod> startupClass = startupItem.asStartupClass();
+    if (startupItem.isSyntheticStartupMethod()) {
+      SyntheticStartupMethod syntheticStartupMethod = startupItem.asSyntheticStartupMethod();
       List<DexProgramClass> syntheticClassesForContext =
           syntheticContextsToSyntheticClasses.getOrDefault(
-              startupClass.getReference(), Collections.emptyList());
+              syntheticStartupMethod.getSyntheticContextType(), Collections.emptyList());
       for (DexProgramClass clazz : syntheticClassesForContext) {
         addClassAndParentClasses(clazz, rewrittenStartupItems, appView);
         addAllMethods(clazz, rewrittenStartupItems);
@@ -164,16 +179,13 @@
   }
 
   private static boolean addClass(
-      DexProgramClass clazz,
-      LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems) {
+      DexProgramClass clazz, LinkedHashSet<StartupItem> rewrittenStartupItems) {
     return rewrittenStartupItems.add(
-        StartupClass.dexBuilder().setClassReference(clazz.getType()).build());
+        StartupClass.builder().setClassReference(clazz.getType()).build());
   }
 
   private static void addClassAndParentClasses(
-      DexType type,
-      LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems,
-      AppView<?> appView) {
+      DexType type, LinkedHashSet<StartupItem> rewrittenStartupItems, AppView<?> appView) {
     DexProgramClass definition = appView.app().programDefinitionFor(type);
     if (definition != null) {
       addClassAndParentClasses(definition, rewrittenStartupItems, appView);
@@ -181,54 +193,53 @@
   }
 
   private static void addClassAndParentClasses(
-      DexProgramClass clazz,
-      LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems,
-      AppView<?> appView) {
+      DexProgramClass clazz, LinkedHashSet<StartupItem> rewrittenStartupItems, AppView<?> appView) {
     if (addClass(clazz, rewrittenStartupItems)) {
       addParentClasses(clazz, rewrittenStartupItems, appView);
     }
   }
 
   private static void addParentClasses(
-      DexProgramClass clazz,
-      LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems,
-      AppView<?> appView) {
+      DexProgramClass clazz, LinkedHashSet<StartupItem> rewrittenStartupItems, AppView<?> appView) {
     clazz.forEachImmediateSupertype(
         supertype -> addClassAndParentClasses(supertype, rewrittenStartupItems, appView));
   }
 
   private static void addAllMethods(
-      DexProgramClass clazz,
-      LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems) {
+      DexProgramClass clazz, LinkedHashSet<StartupItem> rewrittenStartupItems) {
     clazz.forEachProgramMethod(
         method ->
             rewrittenStartupItems.add(
-                StartupMethod.dexBuilder().setMethodReference(method.getReference()).build()));
+                StartupMethod.builder().setMethodReference(method.getReference()).build()));
   }
 
   @Override
   public StartupOrder withoutPrunedItems(PrunedItems prunedItems, SyntheticItems syntheticItems) {
-    LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems =
-        new LinkedHashSet<>(startupItems.size());
+    LinkedHashSet<StartupItem> rewrittenStartupItems = new LinkedHashSet<>(startupItems.size());
     LazyBox<Set<DexType>> contextsOfLiveSynthetics =
         new LazyBox<>(
             () -> computeContextsOfLiveSynthetics(prunedItems.getPrunedApp(), syntheticItems));
-    for (StartupItem<DexType, DexMethod, ?> startupItem : startupItems) {
+    for (StartupItem startupItem : startupItems) {
       // Only prune non-synthetic classes, since the pruning of a class does not imply that all
       // classes synthesized from it have been pruned.
-      if (startupItem.isSynthetic()) {
-        assert startupItem.isStartupClass();
-        StartupClass<DexType, DexMethod> startupClass = startupItem.asStartupClass();
-        if (contextsOfLiveSynthetics.computeIfAbsent().contains(startupClass.getReference())) {
-          rewrittenStartupItems.add(startupClass);
-        }
-      } else {
-        DexReference reference =
-            startupItem.apply(StartupClass::getReference, StartupMethod::getReference);
-        if (!prunedItems.isRemoved(reference)) {
-          rewrittenStartupItems.add(startupItem);
-        }
-      }
+      startupItem.accept(
+          startupClass -> {
+            if (!prunedItems.isRemoved(startupClass.getReference())) {
+              rewrittenStartupItems.add(startupItem);
+            }
+          },
+          startupMethod -> {
+            if (!prunedItems.isRemoved(startupMethod.getReference())) {
+              rewrittenStartupItems.add(startupItem);
+            }
+          },
+          syntheticStartupMethod -> {
+            if (contextsOfLiveSynthetics
+                .computeIfAbsent()
+                .contains(syntheticStartupMethod.getSyntheticContextType())) {
+              rewrittenStartupItems.add(syntheticStartupMethod);
+            }
+          });
     }
     return createNonEmpty(rewrittenStartupItems);
   }
@@ -245,8 +256,7 @@
     return contextsOfLiveSynthetics;
   }
 
-  private StartupOrder createNonEmpty(
-      LinkedHashSet<StartupItem<DexType, DexMethod, ?>> startupItems) {
+  private StartupOrder createNonEmpty(LinkedHashSet<StartupItem> startupItems) {
     if (startupItems.isEmpty()) {
       assert false;
       return empty();
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupClass.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupClass.java
deleted file mode 100644
index b6c6311..0000000
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupClass.java
+++ /dev/null
@@ -1,82 +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.experimental.startup;
-
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.MethodReference;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-// TODO(b/238173796): When updating the compiler to have support for taking a list of startup
-//  methods, this class may likely be removed along with the StartupItem class, so that only
-//  StartupMethod remains.
-public class StartupClass<C, M> extends StartupItem<C, M, C> {
-
-  public StartupClass(int flags, C reference) {
-    super(flags, reference);
-  }
-
-  public static <C, M> Builder<C, M> builder() {
-    return new Builder<>();
-  }
-
-  public static Builder<DexType, DexMethod> dexBuilder() {
-    return new Builder<>();
-  }
-
-  public static Builder<ClassReference, MethodReference> referenceBuilder() {
-    return new Builder<>();
-  }
-
-  @Override
-  public void accept(
-      Consumer<StartupClass<C, M>> classConsumer, Consumer<StartupMethod<C, M>> methodConsumer) {
-    classConsumer.accept(this);
-  }
-
-  @Override
-  public <T> T apply(
-      Function<StartupClass<C, M>, T> classFunction,
-      Function<StartupMethod<C, M>, T> methodFunction) {
-    return classFunction.apply(this);
-  }
-
-  @Override
-  public boolean isStartupClass() {
-    return true;
-  }
-
-  @Override
-  public StartupClass<C, M> asStartupClass() {
-    return this;
-  }
-
-  @Override
-  public void serializeToString(
-      StringBuilder builder,
-      Function<C, String> classSerializer,
-      Function<M, String> methodSerializer) {
-    if (isSynthetic()) {
-      builder.append('S');
-    }
-    builder.append(classSerializer.apply(getReference()));
-  }
-
-  public static class Builder<C, M> extends StartupItem.Builder<C, M, Builder<C, M>> {
-
-    @Override
-    public Builder<C, M> setMethodReference(M reference) {
-      throw new Unreachable();
-    }
-
-    @Override
-    public StartupClass<C, M> build() {
-      return new StartupClass<>(flags, classReference);
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupCompleteness.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupCompleteness.java
index ca0b88f..6ef463d 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupCompleteness.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupCompleteness.java
@@ -4,15 +4,16 @@
 
 package com.android.tools.r8.experimental.startup;
 
+import com.android.tools.r8.experimental.startup.profile.StartupItem;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ThrowNullCode;
+import com.android.tools.r8.profile.art.ArtProfileBuilderUtils.SyntheticToSyntheticContextGeneralization;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMaps;
@@ -27,11 +28,16 @@
   private final StartupOrder startupOrder;
 
   private StartupCompleteness(AppView<?> appView) {
+    SyntheticToSyntheticContextGeneralization syntheticToSyntheticContextGeneralization =
+        appView.enableWholeProgramOptimizations()
+            ? SyntheticToSyntheticContextGeneralization.createForR8()
+            : SyntheticToSyntheticContextGeneralization.createForD8();
     this.appView = appView;
     this.startupOrder =
         appView.hasClassHierarchy()
             ? appView.appInfoWithClassHierarchy().getStartupOrder()
-            : StartupOrder.createInitialStartupOrder(appView.options());
+            : StartupOrder.createInitialStartupOrder(
+                appView.options(), null, syntheticToSyntheticContextGeneralization);
   }
 
   /**
@@ -78,26 +84,20 @@
     Set<DexReference> startupItems = Sets.newIdentityHashSet();
     Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses =
         appView.getSyntheticItems().computeSyntheticContextsToSyntheticClasses(appView);
-    for (StartupItem<DexType, DexMethod, ?> startupItem : startupOrder.getItems()) {
-      if (startupItem.isSynthetic()) {
-        assert startupItem.isStartupClass();
-        List<DexProgramClass> syntheticClasses =
-            syntheticContextsToSyntheticClasses.getOrDefault(
-                startupItem.asStartupClass().getReference(), Collections.emptyList());
-        for (DexProgramClass syntheticClass : syntheticClasses) {
-          startupItems.add(syntheticClass.getType());
-          syntheticClass.forEachProgramMethod(method -> startupItems.add(method.getReference()));
-        }
-      } else {
-        if (startupItem.isStartupClass()) {
-          StartupClass<DexType, DexMethod> startupClass = startupItem.asStartupClass();
-          startupItems.add(startupClass.getReference());
-        } else {
-          assert startupItem.isStartupMethod();
-          StartupMethod<DexType, DexMethod> startupMethod = startupItem.asStartupMethod();
-          startupItems.add(startupMethod.getReference());
-        }
-      }
+    for (StartupItem startupItem : startupOrder.getItems()) {
+      startupItem.accept(
+          startupClass -> startupItems.add(startupClass.getReference()),
+          startupMethod -> startupItems.add(startupMethod.getReference()),
+          syntheticStartupMethod -> {
+            List<DexProgramClass> syntheticClasses =
+                syntheticContextsToSyntheticClasses.getOrDefault(
+                    syntheticStartupMethod.getSyntheticContextType(), Collections.emptyList());
+            for (DexProgramClass syntheticClass : syntheticClasses) {
+              startupItems.add(syntheticClass.getType());
+              syntheticClass.forEachProgramMethod(
+                  method -> startupItems.add(method.getReference()));
+            }
+          });
     }
     return startupItems;
   }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupConfigurationParser.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupConfigurationParser.java
deleted file mode 100644
index 79ebc1e..0000000
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupConfigurationParser.java
+++ /dev/null
@@ -1,163 +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.experimental.startup;
-
-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.references.ClassReference;
-import com.android.tools.r8.references.MethodReference;
-import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.references.TypeReference;
-import com.android.tools.r8.utils.DescriptorUtils;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-public class StartupConfigurationParser<C, M, T> {
-
-  interface MethodFactory<C, M, T> {
-
-    M createMethod(
-        C methodHolder, String methodName, List<T> methodParameterTypes, T methodReturnType);
-  }
-
-  private final Function<String, C> classFactory;
-  private final MethodFactory<C, M, T> methodFactory;
-  private final Function<String, T> typeFactory;
-
-  StartupConfigurationParser(
-      Function<String, C> classFactory,
-      MethodFactory<C, M, T> methodFactory,
-      Function<String, T> typeFactory) {
-    this.classFactory = classFactory;
-    this.methodFactory = methodFactory;
-    this.typeFactory = typeFactory;
-  }
-
-  public static StartupConfigurationParser<DexType, DexMethod, DexType> createDexParser(
-      DexItemFactory dexItemFactory) {
-    return new StartupConfigurationParser<>(
-        dexItemFactory::createType,
-        (methodHolder, methodName, methodParameters, methodReturnType) ->
-            dexItemFactory.createMethod(
-                methodHolder,
-                dexItemFactory.createProto(methodReturnType, methodParameters),
-                dexItemFactory.createString(methodName)),
-        dexItemFactory::createType);
-  }
-
-  public static StartupConfigurationParser<ClassReference, MethodReference, TypeReference>
-      createReferenceParser() {
-    return new StartupConfigurationParser<>(
-        Reference::classFromDescriptor, Reference::method, Reference::returnTypeFromDescriptor);
-  }
-
-  public void parseLines(
-      List<String> startupDescriptors,
-      Consumer<? super StartupClass<C, M>> startupClassConsumer,
-      Consumer<? super StartupMethod<C, M>> startupMethodConsumer,
-      Consumer<String> parseErrorHandler) {
-    for (String startupDescriptor : startupDescriptors) {
-      if (!startupDescriptor.isEmpty()) {
-        parseLine(
-            startupDescriptor, startupClassConsumer, startupMethodConsumer, parseErrorHandler);
-      }
-    }
-  }
-
-  public void parseLine(
-      String startupDescriptor,
-      Consumer<? super StartupClass<C, M>> startupClassConsumer,
-      Consumer<? super StartupMethod<C, M>> startupMethodConsumer,
-      Consumer<String> parseErrorHandler) {
-    StartupItem.Builder<C, M, ?> startupItemBuilder = StartupItem.builder();
-    startupDescriptor = parseSyntheticFlag(startupDescriptor, startupItemBuilder);
-    parseStartupClassOrMethod(
-        startupDescriptor,
-        startupItemBuilder,
-        startupClassConsumer,
-        startupMethodConsumer,
-        parseErrorHandler);
-  }
-
-  private static String parseSyntheticFlag(
-      String startupDescriptor, StartupItem.Builder<?, ?, ?> startupItemBuilder) {
-    if (!startupDescriptor.isEmpty() && startupDescriptor.charAt(0) == 'S') {
-      startupItemBuilder.setSynthetic();
-      return startupDescriptor.substring(1);
-    }
-    return startupDescriptor;
-  }
-
-  private void parseStartupClassOrMethod(
-      String startupDescriptor,
-      StartupItem.Builder<C, M, ?> startupItemBuilder,
-      Consumer<? super StartupClass<C, M>> startupClassConsumer,
-      Consumer<? super StartupMethod<C, M>> startupMethodConsumer,
-      Consumer<String> parseErrorHandler) {
-    int arrowStartIndex = getArrowStartIndex(startupDescriptor);
-    if (arrowStartIndex >= 0) {
-      M startupMethod = parseStartupMethodDescriptor(startupDescriptor, arrowStartIndex);
-      if (startupMethod != null) {
-        startupMethodConsumer.accept(
-            startupItemBuilder.setMethodReference(startupMethod).buildStartupMethod());
-      } else {
-        parseErrorHandler.accept(startupDescriptor);
-      }
-    } else {
-      C startupClass = parseStartupClassDescriptor(startupDescriptor);
-      if (startupClass != null) {
-        startupClassConsumer.accept(
-            startupItemBuilder.setClassReference(startupClass).buildStartupClass());
-      } else {
-        parseErrorHandler.accept(startupDescriptor);
-      }
-    }
-  }
-
-  private static int getArrowStartIndex(String startupDescriptor) {
-    return startupDescriptor.indexOf("->");
-  }
-
-  private C parseStartupClassDescriptor(String startupClassDescriptor) {
-    if (DescriptorUtils.isClassDescriptor(startupClassDescriptor)) {
-      return classFactory.apply(startupClassDescriptor);
-    } else {
-      return null;
-    }
-  }
-
-  private M parseStartupMethodDescriptor(String startupMethodDescriptor, int arrowStartIndex) {
-    String classDescriptor = startupMethodDescriptor.substring(0, arrowStartIndex);
-    C methodHolder = parseStartupClassDescriptor(classDescriptor);
-    if (methodHolder == null) {
-      return null;
-    }
-
-    int methodNameStartIndex = arrowStartIndex + 2;
-    String protoWithNameDescriptor = startupMethodDescriptor.substring(methodNameStartIndex);
-    int methodNameEndIndex = protoWithNameDescriptor.indexOf('(');
-    if (methodNameEndIndex <= 0) {
-      return null;
-    }
-    String methodName = protoWithNameDescriptor.substring(0, methodNameEndIndex);
-
-    String protoDescriptor = protoWithNameDescriptor.substring(methodNameEndIndex);
-    return parseStartupMethodProto(methodHolder, methodName, protoDescriptor);
-  }
-
-  private M parseStartupMethodProto(C methodHolder, String methodName, String protoDescriptor) {
-    List<T> parameterTypes = new ArrayList<>();
-    for (String parameterTypeDescriptor :
-        DescriptorUtils.getArgumentTypeDescriptors(protoDescriptor)) {
-      parameterTypes.add(typeFactory.apply(parameterTypeDescriptor));
-    }
-    String returnTypeDescriptor = DescriptorUtils.getReturnTypeDescriptor(protoDescriptor);
-    T returnType = typeFactory.apply(returnTypeDescriptor);
-    return methodFactory.createMethod(methodHolder, methodName, parameterTypes, returnType);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupItem.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupItem.java
deleted file mode 100644
index e2ea09b..0000000
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupItem.java
+++ /dev/null
@@ -1,165 +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.experimental.startup;
-
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-public abstract class StartupItem<C, M, R> {
-
-  private static final int FLAG_SYNTHETIC = 1;
-
-  protected final int flags;
-  protected final R reference;
-
-  public StartupItem(int flags, R reference) {
-    this.flags = flags;
-    this.reference = reference;
-  }
-
-  public abstract void accept(
-      Consumer<StartupClass<C, M>> classConsumer, Consumer<StartupMethod<C, M>> methodConsumer);
-
-  public abstract <T> T apply(
-      Function<StartupClass<C, M>, T> classFunction,
-      Function<StartupMethod<C, M>, T> methodFunction);
-
-  public boolean isStartupClass() {
-    return false;
-  }
-
-  public StartupClass<C, M> asStartupClass() {
-    return null;
-  }
-
-  public boolean isStartupMethod() {
-    return false;
-  }
-
-  public StartupMethod<C, M> asStartupMethod() {
-    return null;
-  }
-
-  public static <C, M> Builder<C, M, ?> builder() {
-    return new Builder<>();
-  }
-
-  public static Builder<DexType, DexMethod, ?> dexBuilder() {
-    return new Builder<>();
-  }
-
-  public int getFlags() {
-    return flags;
-  }
-
-  public R getReference() {
-    return reference;
-  }
-
-  public boolean isSynthetic() {
-    return (flags & FLAG_SYNTHETIC) != 0;
-  }
-
-  public abstract void serializeToString(
-      StringBuilder builder,
-      Function<C, String> classSerializer,
-      Function<M, String> methodSerializer);
-
-  @Override
-  public boolean equals(Object obj) {
-    if (this == obj) {
-      return true;
-    }
-    if (obj == null || getClass() != obj.getClass()) {
-      return false;
-    }
-    StartupItem<?, ?, ?> startupItem = (StartupItem<?, ?, ?>) obj;
-    return flags == startupItem.flags && reference.equals(startupItem.reference);
-  }
-
-  @Override
-  public int hashCode() {
-    return (reference.hashCode() << 1) | flags;
-  }
-
-  @Override
-  public String toString() {
-    StringBuilder builder = new StringBuilder();
-    if (isSynthetic()) {
-      builder.append('S');
-    }
-    builder.append(reference);
-    return builder.toString();
-  }
-
-  public static class Builder<C, M, B extends Builder<C, M, B>> {
-
-    protected int flags;
-    protected C classReference;
-    protected M methodReference;
-
-    public B applyIf(boolean condition, Consumer<B> thenConsumer, Consumer<B> elseConsumer) {
-      if (condition) {
-        thenConsumer.accept(self());
-      } else {
-        elseConsumer.accept(self());
-      }
-      return self();
-    }
-
-    public B setFlags(int flags) {
-      this.flags = flags;
-      return self();
-    }
-
-    public B setClassReference(C reference) {
-      this.classReference = reference;
-      return self();
-    }
-
-    public B setMethodReference(M reference) {
-      this.methodReference = reference;
-      return self();
-    }
-
-    public B setSynthetic() {
-      this.flags |= FLAG_SYNTHETIC;
-      return self();
-    }
-
-    public B setSynthetic(boolean synthetic) {
-      if (synthetic) {
-        return setSynthetic();
-      }
-      assert (flags & FLAG_SYNTHETIC) == 0;
-      return self();
-    }
-
-    public StartupItem<C, M, ?> build() {
-      if (classReference != null) {
-        return buildStartupClass();
-      } else {
-        return buildStartupMethod();
-      }
-    }
-
-    public StartupClass<C, M> buildStartupClass() {
-      assert classReference != null;
-      return new StartupClass<>(flags, classReference);
-    }
-
-    public StartupMethod<C, M> buildStartupMethod() {
-      assert methodReference != null;
-      return new StartupMethod<>(flags, methodReference);
-    }
-
-    @SuppressWarnings("unchecked")
-    public B self() {
-      return (B) this;
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupMethod.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupMethod.java
deleted file mode 100644
index 00ddca9..0000000
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupMethod.java
+++ /dev/null
@@ -1,69 +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.experimental.startup;
-
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.MethodReference;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-public class StartupMethod<C, M> extends StartupItem<C, M, M> {
-
-  public StartupMethod(int flags, M reference) {
-    super(flags, reference);
-  }
-
-  public static Builder<ClassReference, MethodReference> referenceBuilder() {
-    return new Builder<>();
-  }
-
-  @Override
-  public void accept(
-      Consumer<StartupClass<C, M>> classConsumer, Consumer<StartupMethod<C, M>> methodConsumer) {
-    methodConsumer.accept(this);
-  }
-
-  @Override
-  public <T> T apply(
-      Function<StartupClass<C, M>, T> classFunction,
-      Function<StartupMethod<C, M>, T> methodFunction) {
-    return methodFunction.apply(this);
-  }
-
-  @Override
-  public boolean isStartupMethod() {
-    return true;
-  }
-
-  @Override
-  public StartupMethod<C, M> asStartupMethod() {
-    return this;
-  }
-
-  @Override
-  public void serializeToString(
-      StringBuilder builder,
-      Function<C, String> classSerializer,
-      Function<M, String> methodSerializer) {
-    if (isSynthetic()) {
-      builder.append('S');
-    }
-    builder.append(methodSerializer.apply(getReference()));
-  }
-
-  public static class Builder<C, M> extends StartupItem.Builder<C, M, Builder<C, M>> {
-
-    @Override
-    public Builder<C, M> setClassReference(C reference) {
-      throw new Unreachable();
-    }
-
-    @Override
-    public StartupMethod<C, M> build() {
-      return new StartupMethod<>(flags, methodReference);
-    }
-  }
-}
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 86df003..01f91e0 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,13 +6,13 @@
 
 import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault;
 
-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.InternalOptions;
 import com.android.tools.r8.utils.SystemPropertyUtils;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Paths;
+import java.util.Collection;
+import java.util.Collections;
 
 public class StartupOptions {
 
@@ -52,28 +52,18 @@
   private boolean enableStartupLayoutOptimizations =
       parseSystemPropertyForDevelopmentOrDefault("com.android.tools.r8.startup.layout", true);
 
-  private StartupProfileProvider startupProfileProvider =
-      SystemPropertyUtils.applySystemProperty(
-          "com.android.tools.r8.startup.profile",
-          propertyValue ->
-              new StartupProfileProvider() {
-                @Override
-                public String get() {
-                  return StringResource.fromFile(Paths.get(propertyValue))
-                      .getStringWithRuntimeException();
-                }
+  private Collection<StartupProfileProvider> startupProfileProviders;
 
-                @Override
-                public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) {
-                  throw new Unimplemented();
-                }
-
-                @Override
-                public Origin getOrigin() {
-                  return Origin.unknown();
-                }
-              },
-          () -> null);
+  public StartupOptions(InternalOptions options) {
+    this.startupProfileProviders =
+        SystemPropertyUtils.applySystemProperty(
+            "com.android.tools.r8.startup.profile",
+            propertyValue ->
+                ImmutableList.of(
+                    StartupProfileProviderUtils.createFromHumanReadableArtProfile(
+                        Paths.get(propertyValue))),
+            Collections::emptyList);
+  }
 
   public boolean isMinimalStartupDexEnabled() {
     return enableMinimalStartupDex;
@@ -112,16 +102,17 @@
     return this;
   }
 
-  public boolean hasStartupProfileProvider() {
-    return startupProfileProvider != null;
+  public boolean hasStartupProfileProviders() {
+    return startupProfileProviders != null && !startupProfileProviders.isEmpty();
   }
 
-  public StartupProfileProvider getStartupProfileProvider() {
-    return startupProfileProvider;
+  public Collection<StartupProfileProvider> getStartupProfileProviders() {
+    return startupProfileProviders;
   }
 
-  public StartupOptions setStartupProfileProvider(StartupProfileProvider startupProfileProvider) {
-    this.startupProfileProvider = startupProfileProvider;
+  public StartupOptions setStartupProfileProviders(
+      Collection<StartupProfileProvider> startupProfileProviders) {
+    this.startupProfileProviders = startupProfileProviders;
     return this;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java
index 9f9bf65..b369a94 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java
@@ -4,11 +4,16 @@
 
 package com.android.tools.r8.experimental.startup;
 
+import com.android.tools.r8.experimental.startup.profile.StartupItem;
+import com.android.tools.r8.experimental.startup.profile.StartupProfile;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.profile.art.ArtProfileBuilderUtils.SyntheticToSyntheticContextGeneralization;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.Collection;
@@ -18,21 +23,38 @@
 
   StartupOrder() {}
 
-  public static StartupOrder createInitialStartupOrder(InternalOptions options) {
-    StartupProfile startupProfile = StartupProfile.parseStartupProfile(options);
+  public static StartupOrder createInitialStartupOrder(
+      InternalOptions options,
+      DexDefinitionSupplier definitions,
+      SyntheticToSyntheticContextGeneralization syntheticToSyntheticContextGeneralization) {
+    StartupProfile startupProfile =
+        StartupProfile.parseStartupProfile(
+            options, definitions, syntheticToSyntheticContextGeneralization);
     if (startupProfile == null || startupProfile.getStartupItems().isEmpty()) {
       return empty();
     }
     return new NonEmptyStartupOrder(new LinkedHashSet<>(startupProfile.getStartupItems()));
   }
 
+  public static StartupOrder createInitialStartupOrderForD8(AppView<?> appView) {
+    return createInitialStartupOrder(
+        appView.options(), appView, SyntheticToSyntheticContextGeneralization.createForD8());
+  }
+
+  public static StartupOrder createInitialStartupOrderForR8(DexApplication application) {
+    return createInitialStartupOrder(
+        application.options, application, SyntheticToSyntheticContextGeneralization.createForR8());
+  }
+
   public static StartupOrder empty() {
     return new EmptyStartupOrder();
   }
 
+  public abstract boolean contains(DexMethod method, SyntheticItems syntheticItems);
+
   public abstract boolean contains(DexType type, SyntheticItems syntheticItems);
 
-  public abstract Collection<StartupItem<DexType, DexMethod, ?>> getItems();
+  public abstract Collection<StartupItem> getItems();
 
   public abstract boolean isEmpty();
 
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
deleted file mode 100644
index 6b6076f..0000000
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupProfile.java
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.experimental.startup;
-
-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;
-import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Consumer;
-
-public class StartupProfile {
-
-  private final List<StartupItem<DexType, DexMethod, ?>> startupItems;
-
-  public StartupProfile(List<StartupItem<DexType, DexMethod, ?>> startupItems) {
-    this.startupItems = startupItems;
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  /**
-   * Parses the supplied startup configuration, if any. The startup configuration is a list of class
-   * and method descriptors.
-   *
-   * <p>Example:
-   *
-   * <pre>
-   * Landroidx/compose/runtime/ComposerImpl;->updateValue(Ljava/lang/Object;)V
-   * Landroidx/compose/runtime/ComposerImpl;->updatedNodeCount(I)I
-   * Landroidx/compose/runtime/ComposerImpl;->validateNodeExpected()V
-   * Landroidx/compose/runtime/CompositionImpl;->applyChanges()V
-   * Landroidx/compose/runtime/ComposerKt;->findLocation(Ljava/util/List;I)I
-   * Landroidx/compose/runtime/ComposerImpl;
-   * </pre>
-   */
-  public static StartupProfile parseStartupProfile(InternalOptions options) {
-    if (!options.getStartupOptions().hasStartupProfileProvider()) {
-      return null;
-    }
-    StartupProfileProvider resource = options.getStartupOptions().getStartupProfileProvider();
-    List<String> startupDescriptors = StringUtils.splitLines(resource.get());
-    return createStartupConfigurationFromLines(options, startupDescriptors);
-  }
-
-  public static StartupProfile createStartupConfigurationFromLines(
-      InternalOptions options, List<String> startupDescriptors) {
-    List<StartupItem<DexType, DexMethod, ?>> startupItems = new ArrayList<>();
-    StartupConfigurationParser.createDexParser(options.dexItemFactory())
-        .parseLines(
-            startupDescriptors,
-            startupItems::add,
-            startupItems::add,
-            error ->
-                options.reporter.warning(
-                    new StringDiagnostic(
-                        "Invalid descriptor for startup class or method: " + error)));
-    return new StartupProfile(startupItems);
-  }
-
-  public List<StartupItem<DexType, DexMethod, ?>> getStartupItems() {
-    return startupItems;
-  }
-
-  public String serializeToString() {
-    StringBuilder builder = new StringBuilder();
-    for (StartupItem<DexType, DexMethod, ?> startupItem : startupItems) {
-      startupItem.serializeToString(builder, DexType::toSmaliString, DexMethod::toSmaliString);
-      builder.append('\n');
-    }
-    return builder.toString();
-  }
-
-  public static class Builder {
-
-    private final ImmutableList.Builder<StartupItem<DexType, DexMethod, ?>> startupItemsBuilder =
-        ImmutableList.builder();
-
-    public Builder addStartupItem(StartupItem<DexType, DexMethod, ?> startupItem) {
-      this.startupItemsBuilder.add(startupItem);
-      return this;
-    }
-
-    public Builder addStartupClass(StartupClass<DexType, DexMethod> startupClass) {
-      return addStartupItem(startupClass);
-    }
-
-    public Builder addStartupMethod(StartupMethod<DexType, DexMethod> startupMethod) {
-      return addStartupItem(startupMethod);
-    }
-
-    public Builder apply(Consumer<Builder> consumer) {
-      consumer.accept(this);
-      return this;
-    }
-
-    public StartupProfile build() {
-      return new StartupProfile(startupItemsBuilder.build());
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java
new file mode 100644
index 0000000..3b5b940
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java
@@ -0,0 +1,70 @@
+// 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.experimental.startup;
+
+import com.android.tools.r8.experimental.startup.profile.StartupItem;
+import com.android.tools.r8.experimental.startup.profile.StartupProfile;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.profile.art.ArtProfileBuilderUtils.SyntheticToSyntheticContextGeneralization;
+import com.android.tools.r8.startup.StartupProfileBuilder;
+import com.android.tools.r8.startup.StartupProfileProvider;
+import com.android.tools.r8.startup.diagnostic.MissingStartupProfileItemsDiagnostic;
+import com.android.tools.r8.utils.ConsumerUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.UTF8TextInputStream;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Path;
+
+public class StartupProfileProviderUtils {
+
+  public static StartupProfileProvider createFromHumanReadableArtProfile(Path path) {
+    return new StartupProfileProvider() {
+
+      @Override
+      public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) {
+        try {
+          startupProfileBuilder.addHumanReadableArtProfile(
+              new UTF8TextInputStream(path), ConsumerUtils.emptyConsumer());
+        } catch (IOException e) {
+          throw new UncheckedIOException(e);
+        }
+      }
+
+      @Override
+      public Origin getOrigin() {
+        return new PathOrigin(path);
+      }
+    };
+  }
+
+  /** Serialize the given {@param startupProfileProvider} to a string for writing it to a dump. */
+  public static String serializeToString(
+      InternalOptions options, StartupProfileProvider startupProfileProvider) {
+    // Do not report missing items.
+    MissingStartupProfileItemsDiagnostic.Builder missingItemsDiagnosticBuilder =
+        MissingStartupProfileItemsDiagnostic.Builder.nop();
+    // Do not generalize synthetic items to their synthetic context.
+    SyntheticToSyntheticContextGeneralization syntheticToSyntheticContextGeneralization =
+        SyntheticToSyntheticContextGeneralization.createForD8();
+    StartupProfile.Builder startupProfileBuilder =
+        StartupProfile.builder(
+            options,
+            missingItemsDiagnosticBuilder,
+            startupProfileProvider,
+            syntheticToSyntheticContextGeneralization);
+    // Do not report warnings for lines that cannot be parsed.
+    startupProfileBuilder.setIgnoreWarnings();
+    // Populate the startup profile builder.
+    startupProfileProvider.getStartupProfile(startupProfileBuilder);
+    // Serialize the startup items.
+    StringBuilder resultBuilder = new StringBuilder();
+    for (StartupItem startupItem : startupProfileBuilder.build().getStartupItems()) {
+      resultBuilder.append(startupItem.serializeToString()).append('\n');
+    }
+    return resultBuilder.toString();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentation.java b/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentation.java
index 7e8db58..c3a72b9 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentation.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentation.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
@@ -37,13 +36,11 @@
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.startup.generated.InstrumentationServerFactory;
 import com.android.tools.r8.startup.generated.InstrumentationServerImplFactory;
-import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.DescriptorUtils;
 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.ImmutableList;
-import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -174,20 +171,14 @@
     instructionIterator.positionBeforeNextInstructionThatMatches(not(Instruction::isArgument));
 
     // Insert invoke to record that the enclosing class is a startup class.
-    SyntheticItems syntheticItems = appView.getSyntheticItems();
-    boolean generalizeSyntheticToSyntheticOfSyntheticContexts =
-        startupInstrumentationOptions.isGeneralizationOfSyntheticsToSyntheticContextEnabled()
-            && syntheticItems.isSyntheticClass(method.getHolder());
-    if (method.getDefinition().isClassInitializer()
-        && !generalizeSyntheticToSyntheticOfSyntheticContexts) {
-      DexMethod methodToInvoke = references.addNonSyntheticMethod;
+    if (method.getDefinition().isClassInitializer()) {
       DexType classToPrint = method.getHolderType();
       Value descriptorValue =
           instructionIterator.insertConstStringInstruction(
               appView, code, dexItemFactory.createString(classToPrint.toSmaliString()));
       instructionIterator.add(
           InvokeStatic.builder()
-              .setMethod(methodToInvoke)
+              .setMethod(references.addMethod)
               .setSingleArgument(descriptorValue)
               .setPosition(Position.syntheticNone())
               .build());
@@ -195,26 +186,13 @@
 
     // Insert invoke to record the execution of the current method.
     if (!skipMethodLogging) {
-      DexMethod methodToInvoke;
-      DexReference referenceToPrint;
-      if (generalizeSyntheticToSyntheticOfSyntheticContexts) {
-        Collection<DexType> synthesizingContexts =
-            syntheticItems.getSynthesizingContextTypes(method.getHolderType());
-        assert synthesizingContexts.size() == 1;
-        DexType synthesizingContext = synthesizingContexts.iterator().next();
-        methodToInvoke = references.addSyntheticMethod;
-        referenceToPrint = synthesizingContext;
-      } else {
-        methodToInvoke = references.addNonSyntheticMethod;
-        referenceToPrint = method.getReference();
-      }
-
+      DexReference referenceToPrint = method.getReference();
       Value descriptorValue =
           instructionIterator.insertConstStringInstruction(
               appView, code, dexItemFactory.createString(referenceToPrint.toSmaliString()));
       instructionIterator.add(
           InvokeStatic.builder()
-              .setMethod(methodToInvoke)
+              .setMethod(references.addMethod)
               .setSingleArgument(descriptorValue)
               .setPosition(Position.syntheticNone())
               .build());
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentationOptions.java b/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentationOptions.java
index bfb3f87..0d47783 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentationOptions.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentationOptions.java
@@ -10,14 +10,6 @@
 public class StartupInstrumentationOptions {
 
   /**
-   * When enabled, the instrumentation for synthetics will print the name of the synthetic context
-   * instead of printing the name of the synthetic itself.
-   */
-  private boolean enableGeneralizationOfSyntheticsToSyntheticContext =
-      parseSystemPropertyForDevelopmentOrDefault(
-          "com.android.tools.r8.startup.instrumentation.generalizesynthetics", false);
-
-  /**
    * When enabled, each method will be instrumented to notify the startup InstrumentationServer that
    * it has been executed.
    *
@@ -82,17 +74,6 @@
     return this;
   }
 
-  public boolean isGeneralizationOfSyntheticsToSyntheticContextEnabled() {
-    return enableGeneralizationOfSyntheticsToSyntheticContext;
-  }
-
-  public StartupInstrumentationOptions setEnableGeneralizationOfSyntheticsToSyntheticContext(
-      boolean enableGeneralizationOfSyntheticsToSyntheticContext) {
-    this.enableGeneralizationOfSyntheticsToSyntheticContext =
-        enableGeneralizationOfSyntheticsToSyntheticContext;
-    return this;
-  }
-
   public boolean isStartupInstrumentationEnabled() {
     return enableStartupInstrumentation;
   }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentationReferences.java b/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentationReferences.java
index fbfa5e2..92d3e9c 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentationReferences.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentationReferences.java
@@ -12,23 +12,17 @@
 
   final DexType instrumentationServerType;
   final DexType instrumentationServerImplType;
-  final DexMethod addNonSyntheticMethod;
-  final DexMethod addSyntheticMethod;
+  final DexMethod addMethod;
 
   StartupInstrumentationReferences(DexItemFactory dexItemFactory) {
     instrumentationServerType =
         dexItemFactory.createType("Lcom/android/tools/r8/startup/InstrumentationServer;");
     instrumentationServerImplType =
         dexItemFactory.createType("Lcom/android/tools/r8/startup/InstrumentationServerImpl;");
-    addNonSyntheticMethod =
+    addMethod =
         dexItemFactory.createMethod(
             instrumentationServerImplType,
             dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.stringType),
-            "addNonSyntheticMethod");
-    addSyntheticMethod =
-        dexItemFactory.createMethod(
-            instrumentationServerImplType,
-            dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.stringType),
-            "addSyntheticMethod");
+            "addMethod");
   }
 }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupClass.java b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupClass.java
new file mode 100644
index 0000000..d281a65
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupClass.java
@@ -0,0 +1,112 @@
+// 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.experimental.startup.profile;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.startup.StartupClassBuilder;
+import com.android.tools.r8.utils.ClassReferenceUtils;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class StartupClass extends StartupItem {
+
+  private final DexType type;
+
+  StartupClass(DexType type) {
+    this.type = type;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static Builder builder(DexItemFactory dexItemFactory) {
+    return new Builder(dexItemFactory);
+  }
+
+  @Override
+  public void accept(
+      Consumer<StartupClass> classConsumer,
+      Consumer<StartupMethod> methodConsumer,
+      Consumer<SyntheticStartupMethod> syntheticMethodConsumer) {
+    classConsumer.accept(this);
+  }
+
+  @Override
+  public <T> T apply(
+      Function<StartupClass, T> classFunction,
+      Function<StartupMethod, T> methodFunction,
+      Function<SyntheticStartupMethod, T> syntheticMethodFunction) {
+    return classFunction.apply(this);
+  }
+
+  public DexType getReference() {
+    return type;
+  }
+
+  @Override
+  public boolean isStartupClass() {
+    return true;
+  }
+
+  @Override
+  public StartupClass asStartupClass() {
+    return this;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    StartupClass that = (StartupClass) o;
+    return type == that.type;
+  }
+
+  @Override
+  public int hashCode() {
+    return type.hashCode();
+  }
+
+  @Override
+  public String serializeToString() {
+    return getReference().toDescriptorString();
+  }
+
+  public static class Builder implements StartupClassBuilder {
+
+    private final DexItemFactory dexItemFactory;
+
+    private DexType type;
+
+    Builder() {
+      this(null);
+    }
+
+    Builder(DexItemFactory dexItemFactory) {
+      this.dexItemFactory = dexItemFactory;
+    }
+
+    @Override
+    public Builder setClassReference(ClassReference classReference) {
+      assert dexItemFactory != null;
+      return setClassReference(ClassReferenceUtils.toDexType(classReference, dexItemFactory));
+    }
+
+    public Builder setClassReference(DexType type) {
+      this.type = type;
+      return this;
+    }
+
+    public StartupClass build() {
+      return new StartupClass(type);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupItem.java b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupItem.java
new file mode 100644
index 0000000..b7e6f1c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupItem.java
@@ -0,0 +1,50 @@
+// 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.experimental.startup.profile;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public abstract class StartupItem {
+
+  public abstract void accept(
+      Consumer<StartupClass> classConsumer,
+      Consumer<StartupMethod> methodConsumer,
+      Consumer<SyntheticStartupMethod> syntheticMethodConsumer);
+
+  public abstract <T> T apply(
+      Function<StartupClass, T> classFunction,
+      Function<StartupMethod, T> methodFunction,
+      Function<SyntheticStartupMethod, T> syntheticMethodFunction);
+
+  public boolean isStartupClass() {
+    return false;
+  }
+
+  public StartupClass asStartupClass() {
+    assert false;
+    return null;
+  }
+
+  public boolean isStartupMethod() {
+    return false;
+  }
+
+  public StartupMethod asStartupMethod() {
+    assert false;
+    return null;
+  }
+
+  public boolean isSyntheticStartupMethod() {
+    return false;
+  }
+
+  public SyntheticStartupMethod asSyntheticStartupMethod() {
+    assert false;
+    return null;
+  }
+
+  public abstract String serializeToString();
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupMethod.java b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupMethod.java
new file mode 100644
index 0000000..18045ef
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupMethod.java
@@ -0,0 +1,112 @@
+// 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.experimental.startup.profile;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.startup.StartupMethodBuilder;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class StartupMethod extends StartupItem {
+
+  private final DexMethod method;
+
+  StartupMethod(DexMethod method) {
+    this.method = method;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static Builder builder(DexItemFactory dexItemFactory) {
+    return new Builder(dexItemFactory);
+  }
+
+  @Override
+  public void accept(
+      Consumer<StartupClass> classConsumer,
+      Consumer<StartupMethod> methodConsumer,
+      Consumer<SyntheticStartupMethod> syntheticMethodConsumer) {
+    methodConsumer.accept(this);
+  }
+
+  @Override
+  public <T> T apply(
+      Function<StartupClass, T> classFunction,
+      Function<StartupMethod, T> methodFunction,
+      Function<SyntheticStartupMethod, T> syntheticMethodFunction) {
+    return methodFunction.apply(this);
+  }
+
+  public DexMethod getReference() {
+    return method;
+  }
+
+  @Override
+  public boolean isStartupMethod() {
+    return true;
+  }
+
+  @Override
+  public StartupMethod asStartupMethod() {
+    return this;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    StartupMethod that = (StartupMethod) o;
+    return method == that.method;
+  }
+
+  @Override
+  public int hashCode() {
+    return method.hashCode();
+  }
+
+  @Override
+  public String serializeToString() {
+    return method.toSmaliString();
+  }
+
+  public static class Builder implements StartupMethodBuilder {
+
+    private final DexItemFactory dexItemFactory;
+
+    private DexMethod method;
+
+    Builder() {
+      this(null);
+    }
+
+    Builder(DexItemFactory dexItemFactory) {
+      this.dexItemFactory = dexItemFactory;
+    }
+
+    @Override
+    public Builder setMethodReference(MethodReference classReference) {
+      assert dexItemFactory != null;
+      return setMethodReference(MethodReferenceUtils.toDexMethod(classReference, dexItemFactory));
+    }
+
+    public Builder setMethodReference(DexMethod method) {
+      this.method = method;
+      return this;
+    }
+
+    public StartupMethod build() {
+      return new StartupMethod(method);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfile.java b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfile.java
new file mode 100644
index 0000000..6dd7c65
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfile.java
@@ -0,0 +1,215 @@
+// 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.experimental.startup.profile;
+
+import com.android.tools.r8.TextInputStream;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.profile.art.AlwaysTrueArtProfileRulePredicate;
+import com.android.tools.r8.profile.art.ArtProfileBuilderUtils;
+import com.android.tools.r8.profile.art.ArtProfileBuilderUtils.SyntheticToSyntheticContextGeneralization;
+import com.android.tools.r8.profile.art.ArtProfileRulePredicate;
+import com.android.tools.r8.profile.art.HumanReadableArtProfileParser;
+import com.android.tools.r8.profile.art.HumanReadableArtProfileParserBuilder;
+import com.android.tools.r8.startup.StartupClassBuilder;
+import com.android.tools.r8.startup.StartupMethodBuilder;
+import com.android.tools.r8.startup.StartupProfileBuilder;
+import com.android.tools.r8.startup.StartupProfileProvider;
+import com.android.tools.r8.startup.SyntheticStartupMethodBuilder;
+import com.android.tools.r8.startup.diagnostic.MissingStartupProfileItemsDiagnostic;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class StartupProfile {
+
+  private final LinkedHashSet<StartupItem> startupItems;
+
+  StartupProfile(LinkedHashSet<StartupItem> startupItems) {
+    this.startupItems = startupItems;
+  }
+
+  public static Builder builder(
+      InternalOptions options,
+      MissingStartupProfileItemsDiagnostic.Builder missingItemsDiagnosticBuilder,
+      StartupProfileProvider startupProfileProvider,
+      SyntheticToSyntheticContextGeneralization syntheticToSyntheticContextGeneralization) {
+    return new Builder(
+        options,
+        missingItemsDiagnosticBuilder,
+        startupProfileProvider,
+        syntheticToSyntheticContextGeneralization);
+  }
+
+  public static StartupProfile merge(Collection<StartupProfile> startupProfiles) {
+    LinkedHashSet<StartupItem> mergedStartupItems = new LinkedHashSet<>();
+    for (StartupProfile startupProfile : startupProfiles) {
+      mergedStartupItems.addAll(startupProfile.getStartupItems());
+    }
+    return new StartupProfile(mergedStartupItems);
+  }
+
+  /**
+   * Parses the supplied startup configuration, if any. The startup configuration is a list of class
+   * and method descriptors.
+   *
+   * <p>Example:
+   *
+   * <pre>
+   * Landroidx/compose/runtime/ComposerImpl;->updateValue(Ljava/lang/Object;)V
+   * Landroidx/compose/runtime/ComposerImpl;->updatedNodeCount(I)I
+   * Landroidx/compose/runtime/ComposerImpl;->validateNodeExpected()V
+   * Landroidx/compose/runtime/CompositionImpl;->applyChanges()V
+   * Landroidx/compose/runtime/ComposerKt;->findLocation(Ljava/util/List;I)I
+   * Landroidx/compose/runtime/ComposerImpl;
+   * </pre>
+   */
+  public static StartupProfile parseStartupProfile(
+      InternalOptions options,
+      DexDefinitionSupplier definitions,
+      SyntheticToSyntheticContextGeneralization syntheticToSyntheticContextGeneralization) {
+    if (!options.getStartupOptions().hasStartupProfileProviders()) {
+      return null;
+    }
+    Collection<StartupProfileProvider> startupProfileProviders =
+        options.getStartupOptions().getStartupProfileProviders();
+    List<StartupProfile> startupProfiles = new ArrayList<>(startupProfileProviders.size());
+    for (StartupProfileProvider startupProfileProvider : startupProfileProviders) {
+      MissingStartupProfileItemsDiagnostic.Builder missingItemsDiagnosticBuilder =
+          new MissingStartupProfileItemsDiagnostic.Builder(definitions)
+              .setOrigin(startupProfileProvider.getOrigin());
+      StartupProfile.Builder startupProfileBuilder =
+          StartupProfile.builder(
+              options,
+              missingItemsDiagnosticBuilder,
+              startupProfileProvider,
+              syntheticToSyntheticContextGeneralization);
+      startupProfileProvider.getStartupProfile(startupProfileBuilder);
+      startupProfiles.add(startupProfileBuilder.build());
+      if (missingItemsDiagnosticBuilder.hasMissingStartupItems()) {
+        options.reporter.warning(missingItemsDiagnosticBuilder.build());
+      }
+    }
+    return StartupProfile.merge(startupProfiles);
+  }
+
+  public Collection<StartupItem> getStartupItems() {
+    return startupItems;
+  }
+
+  public static class Builder implements StartupProfileBuilder {
+
+    private final DexItemFactory dexItemFactory;
+    private final MissingStartupProfileItemsDiagnostic.Builder missingItemsDiagnosticBuilder;
+    private Reporter reporter;
+    private final StartupProfileProvider startupProfileProvider;
+    private final SyntheticToSyntheticContextGeneralization
+        syntheticToSyntheticContextGeneralization;
+
+    private final LinkedHashSet<StartupItem> startupItems = new LinkedHashSet<>();
+
+    Builder(
+        InternalOptions options,
+        MissingStartupProfileItemsDiagnostic.Builder missingItemsDiagnosticBuilder,
+        StartupProfileProvider startupProfileProvider,
+        SyntheticToSyntheticContextGeneralization syntheticToSyntheticContextGeneralization) {
+      this.dexItemFactory = options.dexItemFactory();
+      this.missingItemsDiagnosticBuilder = missingItemsDiagnosticBuilder;
+      this.reporter = options.reporter;
+      this.startupProfileProvider = startupProfileProvider;
+      this.syntheticToSyntheticContextGeneralization = syntheticToSyntheticContextGeneralization;
+    }
+
+    @Override
+    public Builder addStartupClass(Consumer<StartupClassBuilder> startupClassBuilderConsumer) {
+      StartupClass.Builder startupClassBuilder = StartupClass.builder(dexItemFactory);
+      startupClassBuilderConsumer.accept(startupClassBuilder);
+      StartupClass startupClass = startupClassBuilder.build();
+      if (missingItemsDiagnosticBuilder.registerStartupClass(startupClass)) {
+        return this;
+      }
+      return addStartupItem(startupClass);
+    }
+
+    @Override
+    public Builder addStartupMethod(Consumer<StartupMethodBuilder> startupMethodBuilderConsumer) {
+      StartupMethod.Builder startupMethodBuilder = StartupMethod.builder(dexItemFactory);
+      startupMethodBuilderConsumer.accept(startupMethodBuilder);
+      StartupMethod startupMethod = startupMethodBuilder.build();
+      if (missingItemsDiagnosticBuilder.registerStartupMethod(startupMethod)) {
+        return this;
+      }
+      return addStartupItem(startupMethod);
+    }
+
+    @Override
+    public StartupProfileBuilder addSyntheticStartupMethod(
+        Consumer<SyntheticStartupMethodBuilder> syntheticStartupMethodBuilderConsumer) {
+      SyntheticStartupMethod.Builder syntheticStartupMethodBuilder =
+          SyntheticStartupMethod.builder(dexItemFactory);
+      syntheticStartupMethodBuilderConsumer.accept(syntheticStartupMethodBuilder);
+      SyntheticStartupMethod syntheticStartupMethod = syntheticStartupMethodBuilder.build();
+      if (missingItemsDiagnosticBuilder.registerSyntheticStartupMethod(syntheticStartupMethod)) {
+        return this;
+      }
+      return addStartupItem(syntheticStartupMethod);
+    }
+
+    @Override
+    public StartupProfileBuilder addHumanReadableArtProfile(
+        TextInputStream textInputStream,
+        Consumer<HumanReadableArtProfileParserBuilder> parserBuilderConsumer) {
+      Box<ArtProfileRulePredicate> rulePredicateBox =
+          new Box<>(new AlwaysTrueArtProfileRulePredicate());
+      parserBuilderConsumer.accept(
+          new HumanReadableArtProfileParserBuilder() {
+            @Override
+            public HumanReadableArtProfileParserBuilder setRulePredicate(
+                ArtProfileRulePredicate rulePredicate) {
+              rulePredicateBox.set(rulePredicate);
+              return this;
+            }
+          });
+
+      HumanReadableArtProfileParser parser =
+          HumanReadableArtProfileParser.builder()
+              .setReporter(reporter)
+              .setProfileBuilder(
+                  ArtProfileBuilderUtils.createBuilderForArtProfileToStartupProfileConversion(
+                      this, rulePredicateBox.get(), syntheticToSyntheticContextGeneralization))
+              .build();
+      parser.parse(textInputStream, startupProfileProvider.getOrigin());
+      return this;
+    }
+
+    private Builder addStartupItem(StartupItem startupItem) {
+      this.startupItems.add(startupItem);
+      return this;
+    }
+
+    public Builder apply(Consumer<Builder> consumer) {
+      consumer.accept(this);
+      return this;
+    }
+
+    public Builder setIgnoreWarnings() {
+      return setReporter(null);
+    }
+
+    public Builder setReporter(Reporter reporter) {
+      this.reporter = reporter;
+      return this;
+    }
+
+    public StartupProfile build() {
+      return new StartupProfile(startupItems);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/profile/SyntheticStartupMethod.java b/src/main/java/com/android/tools/r8/experimental/startup/profile/SyntheticStartupMethod.java
new file mode 100644
index 0000000..c68a198
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/startup/profile/SyntheticStartupMethod.java
@@ -0,0 +1,113 @@
+// 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.experimental.startup.profile;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.startup.SyntheticStartupMethodBuilder;
+import com.android.tools.r8.utils.ClassReferenceUtils;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class SyntheticStartupMethod extends StartupItem {
+
+  private final DexType syntheticContextType;
+
+  SyntheticStartupMethod(DexType syntheticContextType) {
+    this.syntheticContextType = syntheticContextType;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static Builder builder(DexItemFactory dexItemFactory) {
+    return new Builder(dexItemFactory);
+  }
+
+  @Override
+  public void accept(
+      Consumer<StartupClass> classConsumer,
+      Consumer<StartupMethod> methodConsumer,
+      Consumer<SyntheticStartupMethod> syntheticMethodConsumer) {
+    syntheticMethodConsumer.accept(this);
+  }
+
+  @Override
+  public <T> T apply(
+      Function<StartupClass, T> classFunction,
+      Function<StartupMethod, T> methodFunction,
+      Function<SyntheticStartupMethod, T> syntheticMethodFunction) {
+    return syntheticMethodFunction.apply(this);
+  }
+
+  public DexType getSyntheticContextType() {
+    return syntheticContextType;
+  }
+
+  @Override
+  public boolean isSyntheticStartupMethod() {
+    return true;
+  }
+
+  @Override
+  public SyntheticStartupMethod asSyntheticStartupMethod() {
+    return this;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    SyntheticStartupMethod that = (SyntheticStartupMethod) o;
+    return syntheticContextType == that.syntheticContextType;
+  }
+
+  @Override
+  public int hashCode() {
+    return syntheticContextType.hashCode();
+  }
+
+  @Override
+  public String serializeToString() {
+    return 'S' + syntheticContextType.toDescriptorString();
+  }
+
+  public static class Builder implements SyntheticStartupMethodBuilder {
+
+    private final DexItemFactory dexItemFactory;
+
+    private DexType syntheticContextReference;
+
+    Builder() {
+      this(null);
+    }
+
+    Builder(DexItemFactory dexItemFactory) {
+      this.dexItemFactory = dexItemFactory;
+    }
+
+    @Override
+    public Builder setSyntheticContextReference(ClassReference syntheticContextReference) {
+      assert dexItemFactory != null;
+      return setSyntheticContextReference(
+          ClassReferenceUtils.toDexType(syntheticContextReference, dexItemFactory));
+    }
+
+    public Builder setSyntheticContextReference(DexType syntheticContextReference) {
+      this.syntheticContextReference = syntheticContextReference;
+      return this;
+    }
+
+    public SyntheticStartupMethod build() {
+      return new SyntheticStartupMethod(syntheticContextReference);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java b/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
index 451ff92..8e2fbb4 100644
--- a/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
+++ b/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OptionalBool;
 
 public class FeatureSplitBoundaryOptimizationUtils {
 
@@ -54,14 +55,45 @@
       ProgramMethod callee,
       AppView<? extends AppInfoWithClassHierarchy> appView) {
     ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
-    if (classToFeatureSplitMap.isInSameFeatureOrBothInSameBase(callee, caller, appView)) {
-      return true;
+    FeatureSplit callerFeatureSplit = classToFeatureSplitMap.getFeatureSplit(caller, appView);
+    FeatureSplit calleeFeatureSplit = classToFeatureSplitMap.getFeatureSplit(callee, appView);
+
+    // First guarantee that we don't cross any actual feature split boundaries.
+    if (!calleeFeatureSplit.isBase()) {
+      if (calleeFeatureSplit != callerFeatureSplit) {
+        return false;
+      }
     }
-    // Still allow inlining if we inline from the base into a feature.
-    if (classToFeatureSplitMap.isInBase(callee.getHolder(), appView)) {
-      return true;
+
+    // Next perform startup checks.
+    StartupOrder startupOrder = appView.appInfo().getStartupOrder();
+    SyntheticItems syntheticItems = appView.getSyntheticItems();
+    OptionalBool callerIsStartupMethod = isStartupMethod(caller, startupOrder, syntheticItems);
+    if (callerIsStartupMethod.isTrue()) {
+      // If the caller is a startup method, then only allow inlining if the callee is also a startup
+      // method.
+      if (isStartupMethod(callee, startupOrder, syntheticItems).isFalse()) {
+        return false;
+      }
+    } else if (callerIsStartupMethod.isFalse()) {
+      // If the caller is not a startup method, then only allow inlining if the caller is not a
+      // startup class or the callee is a startup class.
+      if (startupOrder.contains(caller.getHolderType(), syntheticItems)
+          && !startupOrder.contains(callee.getHolderType(), syntheticItems)) {
+        return false;
+      }
     }
-    return false;
+    return true;
+  }
+
+  private static OptionalBool isStartupMethod(
+      ProgramMethod method, StartupOrder startupOrder, SyntheticItems syntheticItems) {
+    if (method.getDefinition().isD8R8Synthesized()) {
+      // Due to inadequate rewriting of the startup list during desugaring, we do not give an
+      // accurate result in this case.
+      return OptionalBool.unknown();
+    }
+    return OptionalBool.of(startupOrder.contains(method.getReference(), syntheticItems));
   }
 
   public static boolean isSafeForVerticalClassMerging(
@@ -69,7 +101,26 @@
       DexProgramClass targetClass,
       AppView<? extends AppInfoWithClassHierarchy> appView) {
     ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
-    return classToFeatureSplitMap.isInSameFeatureOrBothInSameBase(
-        sourceClass, targetClass, appView);
+    FeatureSplit sourceFeatureSplit = classToFeatureSplitMap.getFeatureSplit(sourceClass, appView);
+    FeatureSplit targetFeatureSplit = classToFeatureSplitMap.getFeatureSplit(targetClass, appView);
+
+    // First guarantee that we don't cross any actual feature split boundaries.
+    if (targetFeatureSplit.isBase()) {
+      assert sourceFeatureSplit.isBase() : "Unexpected class in base that inherits from feature";
+    } else {
+      if (sourceFeatureSplit != targetFeatureSplit) {
+        return false;
+      }
+    }
+
+    // If the source class is a startup class then require that the target class is also a startup
+    // class.
+    StartupOrder startupOrder = appView.appInfo().getStartupOrder();
+    SyntheticItems syntheticItems = appView.getSyntheticItems();
+    if (startupOrder.contains(sourceClass.getType(), syntheticItems)
+        && !startupOrder.contains(targetClass.getType(), syntheticItems)) {
+      return false;
+    }
+    return true;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index b4926fe..3633506 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -208,7 +208,7 @@
       DexApplication application, MainDexInfo mainDexInfo) {
     ClassToFeatureSplitMap classToFeatureSplitMap =
         ClassToFeatureSplitMap.createInitialClassToFeatureSplitMap(application.options);
-    StartupOrder startupOrder = StartupOrder.createInitialStartupOrder(application.options);
+    StartupOrder startupOrder = StartupOrder.createInitialStartupOrderForR8(application);
     AppInfoWithClassHierarchy appInfo =
         AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
             application,
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 44506fd..bfa111e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -304,6 +304,7 @@
   public final DexString reflectiveOperationExceptionDescriptor =
       createString("Ljava/lang/ReflectiveOperationException;");
   public final DexString kotlinMetadataDescriptor = createString("Lkotlin/Metadata;");
+  public final DexString kotlinJvmNameDescriptor = createString("Lkotlin/jvm/JvmName;");
 
   public final DexString intFieldUpdaterDescriptor =
       createString("Ljava/util/concurrent/atomic/AtomicIntegerFieldUpdater;");
@@ -490,6 +491,7 @@
   public final DexType reflectiveOperationExceptionType =
       createStaticallyKnownType(reflectiveOperationExceptionDescriptor);
   public final DexType kotlinMetadataType = createStaticallyKnownType(kotlinMetadataDescriptor);
+  public final DexType kotlinJvmNameType = createStaticallyKnownType(kotlinJvmNameDescriptor);
 
   public final DexType javaIoFileType = createStaticallyKnownType("Ljava/io/File;");
   public final DexType javaMathBigIntegerType = createStaticallyKnownType("Ljava/math/BigInteger;");
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 9a4e70c..4412a67 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -63,7 +63,7 @@
   public BackportedMethodRewriter(AppView<?> appView) {
     assert appView.options().desugarState.isOn();
     this.appView = appView;
-    this.rewritableMethods = new RewritableMethods(appView.options(), appView);
+    this.rewritableMethods = new RewritableMethods(appView);
   }
 
   public boolean hasBackports() {
@@ -114,7 +114,7 @@
     TypeRewriter typeRewriter = options.getTypeRewriter();
     AppView<?> appView = AppView.createForD8(appInfo, typeRewriter, Timing.empty());
     BackportedMethodRewriter.RewritableMethods rewritableMethods =
-        new BackportedMethodRewriter.RewritableMethods(options, appView);
+        new BackportedMethodRewriter.RewritableMethods(appView);
     rewritableMethods.visit(methods::add);
     if (appInfo != null) {
       DesugaredLibraryRetargeter desugaredLibraryRetargeter =
@@ -159,15 +159,15 @@
     // Map backported method to a provider for creating the actual target method (with code).
     private final Map<DexMethod, MethodProvider> rewritable = new IdentityHashMap<>();
 
-    RewritableMethods(InternalOptions options, AppView<?> appView) {
+    RewritableMethods(AppView<?> appView) {
+      InternalOptions options = appView.options();
+      DexItemFactory factory = options.dexItemFactory();
       this.appView = appView;
-      this.typeMinApi = initializeTypeMinApi(appView.dexItemFactory());
-      if (!options.shouldBackportMethods()) {
+      this.typeMinApi = initializeTypeMinApi(factory);
+      if (!options.enableBackportedMethodRewriting()) {
         return;
       }
 
-      DexItemFactory factory = options.itemFactory;
-
       if (options.getMinApiLevel().isLessThan(AndroidApiLevel.K)) {
         initializeAndroidKMethodProviders(factory);
         if (typeIsAbsentOrPresentWithoutBackportsFrom(factory.objectsType, AndroidApiLevel.K)) {
@@ -265,6 +265,11 @@
           || appView
               .options()
               .machineDesugaredLibrarySpecification
+              .getEmulatedInterfaces()
+              .containsKey(type)
+          || appView
+              .options()
+              .machineDesugaredLibrarySpecification
               .getMaintainType()
               .contains(type);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index 3e41a70..f68df08 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -79,7 +79,7 @@
       return;
     }
     this.nestBasedAccessDesugaring = NestBasedAccessDesugaring.create(appView);
-    BackportedMethodRewriter backportedMethodRewriter = null;
+    BackportedMethodRewriter backportedMethodRewriter = new BackportedMethodRewriter(appView);
     desugaredLibraryRetargeter =
         appView.options().machineDesugaredLibrarySpecification.hasRetargeting()
             ? new DesugaredLibraryRetargeter(appView)
@@ -87,9 +87,6 @@
     if (desugaredLibraryRetargeter != null) {
       desugarings.add(desugaredLibraryRetargeter);
     }
-    if (appView.options().enableBackportedMethodRewriting()) {
-      backportedMethodRewriter = new BackportedMethodRewriter(appView);
-    }
     if (appView.options().apiModelingOptions().enableOutliningOfMethods) {
       yieldingDesugarings.add(new ApiInvokeOutlinerDesugaring(appView, apiLevelCompute));
     }
@@ -127,7 +124,7 @@
     desugarings.add(new InvokeToPrivateRewriter());
     desugarings.add(new StringConcatInstructionDesugaring(appView));
     desugarings.add(new BufferCovariantReturnTypeRewriter(appView));
-    if (backportedMethodRewriter != null && backportedMethodRewriter.hasBackports()) {
+    if (backportedMethodRewriter.hasBackports()) {
       desugarings.add(backportedMethodRewriter);
     }
     if (nestBasedAccessDesugaring != null) {
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
index afddbae..34a8e9d 100644
--- 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
@@ -39,10 +39,14 @@
   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]);
+    Path customConversionsJar = Paths.get(args[2]);
+    Path androidJar = Paths.get(args[3]);
+    Path output = Paths.get(args[4]);
     convertMultiLevelAnythingToMachineSpecification(
-        jsonFile, ImmutableSet.of(desugaredLibraryJar), ImmutableSet.of(androidJar), output);
+        jsonFile,
+        ImmutableSet.of(desugaredLibraryJar, customConversionsJar),
+        ImmutableSet.of(androidJar),
+        output);
   }
 
   public static void convertMultiLevelAnythingToMachineSpecification(
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 6dc28aa..3801d7c 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
@@ -351,6 +351,9 @@
   private ConstraintWithTarget forFieldInstruction(DexField field, ProgramMethod context) {
     DexField lookup = graphLens.lookupField(field);
     FieldResolutionResult fieldResolutionResult = appView.appInfo().resolveField(lookup);
+    if (fieldResolutionResult.isMultiFieldResolutionResult()) {
+      return ConstraintWithTarget.NEVER;
+    }
     return forResolvedMember(
         fieldResolutionResult.getInitialResolutionHolder(),
         context,
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
index f112b4f..3daa33d 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
@@ -70,6 +70,10 @@
     return packageName;
   }
 
+  public String getModuleName() {
+    return packageInfo.getModuleName();
+  }
+
   @Override
   public int[] getMetadataVersion() {
     return metadataVersion;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
index 63e7c95..f5b40c2 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
@@ -121,7 +121,9 @@
       AppView<?> appView) {
     // TODO(b/154348683): Check method for flags to pass in.
     boolean rewritten = false;
-    String finalName = this.name;
+    String finalName = name;
+    // Only rewrite the kotlin method name if it was equal to the method name when reading the
+    // metadata.
     if (method != null) {
       String methodName = method.getReference().name.toString();
       String rewrittenName = appView.getNamingLens().lookupName(method.getReference()).toString();
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
index 6c96b8d..62460ae 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
@@ -18,7 +18,6 @@
 // Holds information about Metadata.MultiFileClassPartInfo
 public class KotlinMultiFileClassPartInfo implements KotlinClassLevelInfo {
 
-  // TODO(b/157630779): Maybe model facadeClassName.
   private final String facadeClassName;
   private final KotlinPackageInfo packageInfo;
   private final String packageName;
@@ -78,6 +77,10 @@
     return packageName;
   }
 
+  public String getModuleName() {
+    return packageInfo.getModuleName();
+  }
+
   @Override
   public int[] getMetadataVersion() {
     return metadataVersion;
@@ -87,4 +90,8 @@
   public void trace(DexDefinitionSupplier definitionSupplier) {
     packageInfo.trace(definitionSupplier);
   }
+
+  public String getFacadeClassName() {
+    return facadeClassName;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
index f1e2573..a0f0e81 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
@@ -97,4 +97,8 @@
     containerInfo.trace(definitionSupplier);
     localDelegatedProperties.trace(definitionSupplier);
   }
+
+  public String getModuleName() {
+    return moduleName;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java b/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java
new file mode 100644
index 0000000..7686a1e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java
@@ -0,0 +1,188 @@
+// 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.naming;
+
+import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
+
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
+import com.android.tools.r8.kotlin.KotlinMultiFileClassPartInfo;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.Pair;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import kotlinx.metadata.jvm.KotlinModuleMetadata.Writer;
+
+/**
+ * The kotlin module synthesizer will scan through all file facades and multiclass files to figure
+ * out the residual package destination of these and bucket them into their original module names.
+ */
+public class KotlinModuleSynthesizer {
+
+  private final AppView<?> appView;
+
+  public KotlinModuleSynthesizer(AppView<?> appView) {
+    this.appView = appView;
+  }
+
+  public boolean isKotlinModuleFile(DataEntryResource file) {
+    return file.getName().endsWith(".kotlin_module");
+  }
+
+  public List<DataEntryResource> synthesizeKotlinModuleFiles() {
+    Map<String, KotlinModuleInfoBuilder> kotlinModuleBuilders = new HashMap<>();
+    // We cannot obtain the module name for multi class file facades. But, we can for a multi class
+    // part obtain both the module name and the multi class facade. We therefore iterate over all
+    // classes to find a multi class facade -> module name mapping, and then iterate over all
+    // classes to assign multi class facades to modules.
+    Map<String, String> moduleNamesForParts = new HashMap<>();
+    for (DexProgramClass clazz : appView.app().classesWithDeterministicOrder()) {
+      KotlinClassLevelInfo kotlinInfo = clazz.getKotlinInfo();
+      if (kotlinInfo.isFileFacade()) {
+        kotlinModuleBuilders
+            .computeIfAbsent(
+                kotlinInfo.asFileFacade().getModuleName(),
+                moduleName -> new KotlinModuleInfoBuilder(moduleName, appView))
+            .add(clazz);
+      } else if (kotlinInfo.isMultiFileClassPart()) {
+        KotlinMultiFileClassPartInfo kotlinMultiFileClassPartInfo =
+            kotlinInfo.asMultiFileClassPart();
+        moduleNamesForParts.computeIfAbsent(
+            kotlinMultiFileClassPartInfo.getFacadeClassName(),
+            ignored -> kotlinMultiFileClassPartInfo.getModuleName());
+        kotlinModuleBuilders
+            .computeIfAbsent(
+                kotlinMultiFileClassPartInfo.getModuleName(),
+                moduleName -> new KotlinModuleInfoBuilder(moduleName, appView))
+            .add(clazz);
+      }
+    }
+    for (DexProgramClass clazz : appView.app().classesWithDeterministicOrder()) {
+      KotlinClassLevelInfo kotlinInfo = clazz.getKotlinInfo();
+      if (kotlinInfo.isMultiFileFacade()) {
+        DexType originalType = appView.graphLens().getOriginalType(clazz.getType());
+        if (originalType != null) {
+          String moduleNameForPart = moduleNamesForParts.get(originalType.toBinaryName());
+          // If module name is null then we did not find any multi class file parts and therefore
+          // do not have to do anything for the facade.
+          if (moduleNameForPart != null) {
+            KotlinModuleInfoBuilder kotlinModuleInfoBuilder =
+                kotlinModuleBuilders.get(moduleNameForPart);
+            assert kotlinModuleInfoBuilder != null;
+            kotlinModuleInfoBuilder.add(clazz);
+          }
+        }
+      }
+    }
+    if (kotlinModuleBuilders.isEmpty()) {
+      return Collections.emptyList();
+    }
+    List<DataEntryResource> newResources = new ArrayList<>();
+    kotlinModuleBuilders.values().forEach(builder -> builder.build().ifPresent(newResources::add));
+    return newResources;
+  }
+
+  private static class KotlinModuleInfoBuilder {
+
+    private final String moduleName;
+    private final GraphLens graphLens;
+    private final NamingLens namingLens;
+    private final DexItemFactory factory;
+
+    private final Map<String, List<String>> newFacades = new HashMap<>();
+    private final Map<String, List<Pair<String, String>>> multiClassFacadeOriginalToRenamed =
+        new LinkedHashMap<>();
+    private final Map<String, List<String>> multiClassPartToOriginal = new HashMap<>();
+    private final Box<int[]> metadataVersion = new Box<>();
+
+    private KotlinModuleInfoBuilder(String moduleName, AppView<?> appView) {
+      this.moduleName = moduleName;
+      this.graphLens = appView.graphLens();
+      this.namingLens = appView.getNamingLens();
+      this.factory = appView.dexItemFactory();
+    }
+
+    private void add(DexProgramClass clazz) {
+      DexType classType = clazz.getType();
+      KotlinClassLevelInfo classKotlinInfo = clazz.getKotlinInfo();
+      DexType renamedType = namingLens.lookupType(classType, factory);
+      if (classKotlinInfo.isFileFacade()) {
+        metadataVersion.computeIfAbsent(classKotlinInfo::getMetadataVersion);
+        newFacades
+            .computeIfAbsent(renamedType.getPackageName(), ignoreArgument(ArrayList::new))
+            .add(renamedType.toBinaryName());
+      } else if (classKotlinInfo.isMultiFileFacade()) {
+        metadataVersion.computeIfAbsent(classKotlinInfo::getMetadataVersion);
+        DexType originalType = graphLens.getOriginalType(classType);
+        multiClassFacadeOriginalToRenamed
+            .computeIfAbsent(renamedType.getPackageName(), ignoreArgument(ArrayList::new))
+            .add(Pair.create(originalType.toBinaryName(), renamedType.toBinaryName()));
+      } else {
+        assert classKotlinInfo.isMultiFileClassPart();
+        metadataVersion.computeIfAbsent(classKotlinInfo::getMetadataVersion);
+        KotlinMultiFileClassPartInfo classPart = classKotlinInfo.asMultiFileClassPart();
+        multiClassPartToOriginal
+            .computeIfAbsent(classPart.getFacadeClassName(), ignoreArgument(ArrayList::new))
+            .add(renamedType.toBinaryName());
+      }
+    }
+
+    private Optional<DataEntryResource> build() {
+      // If multiClassParts are non empty but multiFileFacade is, then we have no place to put
+      // the parts anyway, so we can just return empty.
+      if (newFacades.isEmpty() && multiClassFacadeOriginalToRenamed.isEmpty()) {
+        return Optional.empty();
+      }
+      assert metadataVersion.isSet();
+      List<String> packagesSorted = new ArrayList<>(newFacades.keySet());
+      for (String newPackage : multiClassFacadeOriginalToRenamed.keySet()) {
+        if (!newFacades.containsKey(newPackage)) {
+          packagesSorted.add(newPackage);
+        }
+      }
+      Collections.sort(packagesSorted);
+      Writer writer = new Writer();
+      for (String newPackage : packagesSorted) {
+        // Calling other visitors than visitPackageParts are currently not supported.
+        // https://github.com/JetBrains/kotlin/blob/master/libraries/kotlinx-metadata/
+        //  jvm/src/kotlinx/metadata/jvm/KotlinModuleMetadata.kt#L70
+        Map<String, String> newMultiFiles = new LinkedHashMap<>();
+        multiClassFacadeOriginalToRenamed
+            .getOrDefault(newPackage, Collections.emptyList())
+            .forEach(
+                pair -> {
+                  String originalName = pair.getFirst();
+                  String rewrittenName = pair.getSecond();
+                  multiClassPartToOriginal
+                      .getOrDefault(originalName, Collections.emptyList())
+                      .forEach(
+                          classPart -> {
+                            newMultiFiles.put(classPart, rewrittenName);
+                          });
+                });
+        writer.visitPackageParts(
+            newPackage,
+            newFacades.getOrDefault(newPackage, Collections.emptyList()),
+            newMultiFiles);
+      }
+      return Optional.of(
+          DataEntryResource.fromBytes(
+              writer.write(metadataVersion.get()).getBytes(),
+              "META-INF/" + moduleName + ".kotlin_module",
+              Origin.unknown()));
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/AlwaysTrueArtProfileRulePredicate.java b/src/main/java/com/android/tools/r8/profile/art/AlwaysTrueArtProfileRulePredicate.java
new file mode 100644
index 0000000..3e26bdc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/AlwaysTrueArtProfileRulePredicate.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.profile.art;
+
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+
+public class AlwaysTrueArtProfileRulePredicate implements ArtProfileRulePredicate {
+
+  @Override
+  public boolean testClassRule(
+      ClassReference classReference, ArtProfileClassRuleInfo classRuleInfo) {
+    return true;
+  }
+
+  @Override
+  public boolean testMethodRule(
+      MethodReference methodReference, ArtProfileMethodRuleInfo methodRuleInfo) {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileBuilder.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileBuilder.java
new file mode 100644
index 0000000..fbe9d78
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileBuilder.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.profile.art;
+
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+
+public interface ArtProfileBuilder {
+
+  void addClassRule(ClassReference classReference, ArtProfileClassRuleInfo classRuleInfo);
+
+  void addMethodRule(MethodReference methodReference, ArtProfileMethodRuleInfo methodRuleInfo);
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileBuilderUtils.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileBuilderUtils.java
new file mode 100644
index 0000000..3426e7b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileBuilderUtils.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.profile.art;
+
+import static com.android.tools.r8.synthesis.SyntheticNaming.COMPANION_CLASS_SUFFIX;
+import static com.android.tools.r8.synthesis.SyntheticNaming.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR;
+
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.startup.StartupProfileBuilder;
+
+public class ArtProfileBuilderUtils {
+
+  public interface SyntheticToSyntheticContextGeneralization {
+
+    ClassReference getSyntheticContextReference(ClassReference classReference);
+
+    /**
+     * When a startup profile is given to D8, the program input should be dex and the startup
+     * profile should have been generated by launching the dex that is given on input. Therefore,
+     * synthetic items in the ART profile should be present in the program input to D8 with the
+     * exact same synthetic names as in the ART profile. This means that there is no need to
+     * generalize synthetic items to their synthetic context.
+     */
+    static SyntheticToSyntheticContextGeneralization createForD8() {
+      return classReference -> null;
+    }
+
+    /**
+     * When a startup profile is given to R8, the program input is class files and the startup
+     * profile should have been generated by dexing the program input (in release and intermediate
+     * mode) and then launching the resulting app. The synthetic items in the resulting ART profile
+     * do not exist in the program input to R8 (and the same synthetics may receive different names
+     * in the R8 compilation). Therefore, synthetic items in the ART profile are generalized into
+     * matching all synthetics from their synthetic context.
+     */
+    static SyntheticToSyntheticContextGeneralization createForR8() {
+      return classReference -> {
+        // TODO(b/243777722): Move this logic into synthetic items and extend the mapping from
+        //  synthetic classes to their synthetic context to all synthetic kinds.
+        String classDescriptor = classReference.getDescriptor();
+        for (int i = 1; i < classDescriptor.length() - 1; i++) {
+          if (classDescriptor.charAt(i) != '$') {
+            continue;
+          }
+          if (classDescriptor.regionMatches(
+                  i, COMPANION_CLASS_SUFFIX, 0, COMPANION_CLASS_SUFFIX.length())
+              || classDescriptor.regionMatches(
+                  i,
+                  EXTERNAL_SYNTHETIC_CLASS_SEPARATOR,
+                  0,
+                  EXTERNAL_SYNTHETIC_CLASS_SEPARATOR.length())) {
+            return Reference.classFromDescriptor(classDescriptor.substring(0, i) + ";");
+          }
+        }
+        return null;
+      };
+    }
+  }
+
+  /**
+   * Helper for creating an {@link ArtProfileBuilder} that performs callbacks on the given {@param
+   * startupProfileBuilder}.
+   */
+  public static ArtProfileBuilder createBuilderForArtProfileToStartupProfileConversion(
+      StartupProfileBuilder startupProfileBuilder,
+      ArtProfileRulePredicate rulePredicate,
+      SyntheticToSyntheticContextGeneralization syntheticToSyntheticContextGeneralization) {
+    return new ArtProfileBuilder() {
+
+      @Override
+      public void addClassRule(
+          ClassReference classReference, ArtProfileClassRuleInfo classRuleInfo) {
+        if (rulePredicate.testClassRule(classReference, classRuleInfo)) {
+          ClassReference syntheticContextReference =
+              syntheticToSyntheticContextGeneralization.getSyntheticContextReference(
+                  classReference);
+          if (syntheticContextReference == null) {
+            startupProfileBuilder.addStartupClass(
+                startupClassBuilder -> startupClassBuilder.setClassReference(classReference));
+          } else {
+            startupProfileBuilder.addSyntheticStartupMethod(
+                syntheticStartupMethodBuilder ->
+                    syntheticStartupMethodBuilder.setSyntheticContextReference(
+                        syntheticContextReference));
+          }
+        }
+      }
+
+      @Override
+      public void addMethodRule(
+          MethodReference methodReference, ArtProfileMethodRuleInfo methodRuleInfo) {
+        if (rulePredicate.testMethodRule(methodReference, methodRuleInfo)) {
+          ClassReference syntheticContextReference =
+              syntheticToSyntheticContextGeneralization.getSyntheticContextReference(
+                  methodReference.getHolderClass());
+          if (syntheticContextReference == null) {
+            startupProfileBuilder.addStartupMethod(
+                startupMethodBuilder -> startupMethodBuilder.setMethodReference(methodReference));
+          } else {
+            startupProfileBuilder.addSyntheticStartupMethod(
+                syntheticStartupMethodBuilder ->
+                    syntheticStartupMethodBuilder.setSyntheticContextReference(
+                        syntheticContextReference));
+          }
+        }
+      }
+    };
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRuleInfo.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRuleInfo.java
new file mode 100644
index 0000000..7ba6bbe
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRuleInfo.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.profile.art;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public interface ArtProfileClassRuleInfo {}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRuleInfoImpl.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRuleInfoImpl.java
new file mode 100644
index 0000000..7be8869
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRuleInfoImpl.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.profile.art;
+
+public class ArtProfileClassRuleInfoImpl implements ArtProfileClassRuleInfo {
+
+  private static final ArtProfileClassRuleInfoImpl INSTANCE = new ArtProfileClassRuleInfoImpl();
+
+  private ArtProfileClassRuleInfoImpl() {}
+
+  public static ArtProfileClassRuleInfoImpl empty() {
+    return INSTANCE;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleInfo.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleInfo.java
new file mode 100644
index 0000000..6a176ab
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleInfo.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.profile.art;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public interface ArtProfileMethodRuleInfo {
+
+  /** Returns true if this method rule method rule is flagged as hot ('H'). */
+  boolean isHot();
+
+  /** Returns true if this method rule method rule is flagged as startup ('S'). */
+  boolean isStartup();
+
+  /** Returns true if this method rule method rule is flagged as post-startup ('P'). */
+  boolean isPostStartup();
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleInfoImpl.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleInfoImpl.java
new file mode 100644
index 0000000..ca2d241
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleInfoImpl.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.profile.art;
+
+public class ArtProfileMethodRuleInfoImpl implements ArtProfileMethodRuleInfo {
+
+  private static final int FLAG_HOT = 1;
+  private static final int FLAG_STARTUP = 2;
+  private static final int FLAG_POST_STARTUP = 4;
+
+  private final int flags;
+
+  ArtProfileMethodRuleInfoImpl(int flags) {
+    this.flags = flags;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public boolean isEmpty() {
+    return flags == 0;
+  }
+
+  @Override
+  public boolean isHot() {
+    return (flags & FLAG_HOT) != 0;
+  }
+
+  @Override
+  public boolean isStartup() {
+    return (flags & FLAG_STARTUP) != 0;
+  }
+
+  @Override
+  public boolean isPostStartup() {
+    return (flags & FLAG_POST_STARTUP) != 0;
+  }
+
+  public static class Builder {
+
+    private int flags;
+
+    public Builder setHot() {
+      flags |= FLAG_HOT;
+      return this;
+    }
+
+    public Builder setStartup() {
+      flags |= FLAG_STARTUP;
+      return this;
+    }
+
+    public Builder setPostStartup() {
+      flags |= FLAG_POST_STARTUP;
+      return this;
+    }
+
+    public ArtProfileMethodRuleInfoImpl build() {
+      return new ArtProfileMethodRuleInfoImpl(flags);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileRulePredicate.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileRulePredicate.java
new file mode 100644
index 0000000..ea843f2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileRulePredicate.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.profile.art;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+
+@Keep
+public interface ArtProfileRulePredicate {
+
+  boolean testClassRule(ClassReference classReference, ArtProfileClassRuleInfo classRuleInfo);
+
+  boolean testMethodRule(MethodReference methodReference, ArtProfileMethodRuleInfo methodRuleInfo);
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/HumanReadableArtProfileParser.java b/src/main/java/com/android/tools/r8/profile/art/HumanReadableArtProfileParser.java
new file mode 100644
index 0000000..5f0c92b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/HumanReadableArtProfileParser.java
@@ -0,0 +1,132 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.profile.art;
+
+import com.android.tools.r8.TextInputStream;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.profile.art.diagnostic.HumanReadableArtProfileParserErrorDiagnostic;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.ClassReferenceUtils;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.Reporter;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UncheckedIOException;
+
+public class HumanReadableArtProfileParser {
+
+  private final ArtProfileBuilder profileBuilder;
+  private final Reporter reporter;
+
+  HumanReadableArtProfileParser(ArtProfileBuilder profileBuilder, Reporter reporter) {
+    this.profileBuilder = profileBuilder;
+    this.reporter = reporter;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public void parse(TextInputStream textInputStream, Origin origin) {
+    try {
+      try (InputStreamReader inputStreamReader =
+              new InputStreamReader(
+                  textInputStream.getInputStream(), textInputStream.getCharset());
+          BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
+        int lineNumber = 1;
+        while (bufferedReader.ready()) {
+          String rule = bufferedReader.readLine();
+          if (!parseRule(rule)) {
+            parseError(rule, lineNumber, origin);
+          }
+          lineNumber++;
+        }
+      }
+      if (reporter != null) {
+        reporter.failIfPendingErrors();
+      }
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    }
+  }
+
+  private void parseError(String rule, int lineNumber, Origin origin) {
+    if (reporter != null) {
+      reporter.error(new HumanReadableArtProfileParserErrorDiagnostic(rule, lineNumber, origin));
+    }
+  }
+
+  public boolean parseRule(String rule) {
+    ArtProfileMethodRuleInfoImpl.Builder methodRuleInfoBuilder =
+        ArtProfileMethodRuleInfoImpl.builder();
+    rule = parseFlag(rule, 'H', methodRuleInfoBuilder::setHot);
+    rule = parseFlag(rule, 'S', methodRuleInfoBuilder::setStartup);
+    rule = parseFlag(rule, 'P', methodRuleInfoBuilder::setPostStartup);
+    return parseClassOrMethodDescriptor(rule, methodRuleInfoBuilder.build());
+  }
+
+  private static String parseFlag(String rule, char c, Action action) {
+    if (!rule.isEmpty() && rule.charAt(0) == c) {
+      action.execute();
+      return rule.substring(1);
+    }
+    return rule;
+  }
+
+  private boolean parseClassOrMethodDescriptor(
+      String descriptor, ArtProfileMethodRuleInfoImpl methodRuleInfo) {
+    int arrowStartIndex = descriptor.indexOf("->");
+    if (arrowStartIndex >= 0) {
+      return parseMethodRule(descriptor, methodRuleInfo, arrowStartIndex);
+    } else if (methodRuleInfo.isEmpty()) {
+      return parseClassRule(descriptor);
+    } else {
+      return false;
+    }
+  }
+
+  private boolean parseClassRule(String descriptor) {
+    ClassReference classReference = ClassReferenceUtils.parseClassDescriptor(descriptor);
+    if (classReference == null) {
+      return false;
+    }
+    profileBuilder.addClassRule(classReference, ArtProfileClassRuleInfoImpl.empty());
+    return true;
+  }
+
+  private boolean parseMethodRule(
+      String descriptor, ArtProfileMethodRuleInfoImpl methodRuleInfo, int arrowStartIndex) {
+    MethodReference methodReference =
+        MethodReferenceUtils.parseSmaliString(descriptor, arrowStartIndex);
+    if (methodReference == null) {
+      return false;
+    }
+    profileBuilder.addMethodRule(methodReference, methodRuleInfo);
+    return true;
+  }
+
+  public static class Builder {
+
+    private ArtProfileBuilder profileBuilder;
+    private Reporter reporter;
+
+    public Builder setReporter(Reporter reporter) {
+      this.reporter = reporter;
+      return this;
+    }
+
+    public Builder setProfileBuilder(ArtProfileBuilder profileBuilder) {
+      this.profileBuilder = profileBuilder;
+      return this;
+    }
+
+    public HumanReadableArtProfileParser build() {
+      return new HumanReadableArtProfileParser(profileBuilder, reporter);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/HumanReadableArtProfileParserBuilder.java b/src/main/java/com/android/tools/r8/profile/art/HumanReadableArtProfileParserBuilder.java
new file mode 100644
index 0000000..9caa97e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/HumanReadableArtProfileParserBuilder.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.profile.art;
+
+import com.android.tools.r8.Keep;
+
+/**
+ * A builder for configuring a parser for the human-readable ART profile format.
+ *
+ * @see <a href="https://developer.android.com/topic/performance/baselineprofiles">ART Baseline
+ *     Profiles</a>
+ */
+@Keep
+public interface HumanReadableArtProfileParserBuilder {
+
+  /**
+   * Only include rules from the ART profile that satisfies the given {@param rulePredicate}.
+   *
+   * <p>By default, all rules from the ART profile are included.
+   */
+  HumanReadableArtProfileParserBuilder setRulePredicate(ArtProfileRulePredicate rulePredicate);
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/diagnostic/HumanReadableArtProfileParserErrorDiagnostic.java b/src/main/java/com/android/tools/r8/profile/art/diagnostic/HumanReadableArtProfileParserErrorDiagnostic.java
new file mode 100644
index 0000000..d93b5f6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/diagnostic/HumanReadableArtProfileParserErrorDiagnostic.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.profile.art.diagnostic;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@Keep
+public class HumanReadableArtProfileParserErrorDiagnostic implements Diagnostic {
+
+  private final String rule;
+  private final int lineNumber;
+  private final Origin origin;
+
+  public HumanReadableArtProfileParserErrorDiagnostic(String rule, int lineNumber, Origin origin) {
+    this.rule = rule;
+    this.lineNumber = lineNumber;
+    this.origin = origin;
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  @Override
+  public Position getPosition() {
+    return Position.UNKNOWN;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return "Unable to parse rule at line " + lineNumber + " from ART profile: " + rule;
+  }
+}
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 773903d..526fc5f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.shaking.ProguardKeepAttributes.RUNTIME_INVISIBLE_ANNOTATIONS;
 import static com.android.tools.r8.shaking.ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS;
 import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptor;
 
@@ -288,26 +289,23 @@
           || parseUnsupportedOptionAndErr(optionStart)) {
         // Intentionally left empty.
       } else if (acceptString("keepkotlinmetadata")) {
+        String source = "-keepkotlinmetadata";
         ProguardKeepRule keepKotlinMetadata =
-            ProguardKeepRule.builder()
-                .setType(ProguardKeepRuleType.KEEP)
-                .setClassType(ProguardClassType.CLASS)
-                .setOrigin(origin)
-                .setStart(optionStart)
-                .setClassNames(
-                    ProguardClassNameList.builder()
-                        .addClassName(
-                            false, ProguardTypeMatcher.create(dexItemFactory.kotlinMetadataType))
-                        .build())
-                .setMemberRules(Collections.singletonList(ProguardMemberRule.defaultKeepAllRule()))
-                .setSource("-keepkotlinmetadata")
-                .build();
-        // Mark the rule as used to ensure we do not report any information messages if the class
+            ProguardKeepRuleUtils.keepClassAndMembersRule(
+                origin, optionStart, dexItemFactory.kotlinMetadataType, source);
+        ProguardKeepRule keepKotlinJvmNameAnnotation =
+            ProguardKeepRuleUtils.keepClassAndMembersRule(
+                origin, optionStart, dexItemFactory.kotlinJvmNameType, source);
+        // Mark the rules as used to ensure we do not report any information messages if the class
         // is not present.
         keepKotlinMetadata.markAsUsed();
+        keepKotlinJvmNameAnnotation.markAsUsed();
         configurationBuilder.addRule(keepKotlinMetadata);
+        configurationBuilder.addRule(keepKotlinJvmNameAnnotation);
         configurationBuilder.addKeepAttributePatterns(
             Collections.singletonList(RUNTIME_VISIBLE_ANNOTATIONS));
+        configurationBuilder.addKeepAttributePatterns(
+            Collections.singletonList(RUNTIME_INVISIBLE_ANNOTATIONS));
       } else if (acceptString("renamesourcefileattribute")) {
         skipWhitespace();
         if (isOptionalArgumentGiven()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleUtils.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleUtils.java
new file mode 100644
index 0000000..b086470
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleUtils.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.Collections;
+
+public class ProguardKeepRuleUtils {
+
+  public static ProguardKeepRule keepClassAndMembersRule(
+      Origin origin, Position start, DexType type, String source) {
+    return ProguardKeepRule.builder()
+        .setType(ProguardKeepRuleType.KEEP)
+        .setClassType(ProguardClassType.CLASS)
+        .setOrigin(origin)
+        .setStart(start)
+        .setClassNames(
+            ProguardClassNameList.builder()
+                .addClassName(false, ProguardTypeMatcher.create(type))
+                .build())
+        .setMemberRules(Collections.singletonList(ProguardMemberRule.defaultKeepAllRule()))
+        .setSource(source)
+        .build();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/startup/StartupProfileBuilder.java b/src/main/java/com/android/tools/r8/startup/StartupProfileBuilder.java
index d3626a1..47916c1 100644
--- a/src/main/java/com/android/tools/r8/startup/StartupProfileBuilder.java
+++ b/src/main/java/com/android/tools/r8/startup/StartupProfileBuilder.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8.startup;
 
 import com.android.tools.r8.Keep;
+import com.android.tools.r8.TextInputStream;
+import com.android.tools.r8.profile.art.HumanReadableArtProfileParserBuilder;
 import java.util.function.Consumer;
 
 /** Interface for providing a startup profile to the compiler. */
@@ -40,4 +42,15 @@
    */
   StartupProfileBuilder addSyntheticStartupMethod(
       Consumer<SyntheticStartupMethodBuilder> syntheticStartupMethodBuilderConsumer);
+
+  /**
+   * Adds the rules from the given human-readable ART profile to the startup profile and then closes
+   * the stream.
+   *
+   * @see <a href="https://developer.android.com/topic/performance/baselineprofiles">ART Baseline
+   *     Profiles</a>
+   */
+  StartupProfileBuilder addHumanReadableArtProfile(
+      TextInputStream textInputStream,
+      Consumer<HumanReadableArtProfileParserBuilder> parserBuilderConsumer);
 }
diff --git a/src/main/java/com/android/tools/r8/startup/StartupProfileProvider.java b/src/main/java/com/android/tools/r8/startup/StartupProfileProvider.java
index 4d98373..39ad8fc 100644
--- a/src/main/java/com/android/tools/r8/startup/StartupProfileProvider.java
+++ b/src/main/java/com/android/tools/r8/startup/StartupProfileProvider.java
@@ -11,10 +11,6 @@
 @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/diagnostic/MissingStartupProfileItemsDiagnostic.java b/src/main/java/com/android/tools/r8/startup/diagnostic/MissingStartupProfileItemsDiagnostic.java
new file mode 100644
index 0000000..5faf4b7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/startup/diagnostic/MissingStartupProfileItemsDiagnostic.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.startup.diagnostic;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.experimental.startup.profile.StartupClass;
+import com.android.tools.r8.experimental.startup.profile.StartupMethod;
+import com.android.tools.r8.experimental.startup.profile.SyntheticStartupMethod;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+@Keep
+public class MissingStartupProfileItemsDiagnostic implements Diagnostic {
+
+  private final List<DexReference> missingStartupItems;
+  private final Origin origin;
+
+  MissingStartupProfileItemsDiagnostic(List<DexReference> missingStartupItems, Origin origin) {
+    assert !missingStartupItems.isEmpty();
+    this.missingStartupItems = missingStartupItems;
+    this.origin = origin;
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  @Override
+  public Position getPosition() {
+    return Position.UNKNOWN;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    StringBuilder builder = new StringBuilder();
+    Iterator<DexReference> missingStartupItemIterator = missingStartupItems.iterator();
+
+    // Write first missing startup item.
+    writeMissingStartupItem(builder, missingStartupItemIterator.next());
+
+    // Write remaining missing startup items with line separator before.
+    while (missingStartupItemIterator.hasNext()) {
+      writeMissingStartupItem(
+          builder.append(System.lineSeparator()), missingStartupItemIterator.next());
+    }
+
+    return builder.toString();
+  }
+
+  private void writeMissingStartupItem(StringBuilder builder, DexReference missingStartupItem) {
+    missingStartupItem.apply(
+        missingStartupClass -> builder.append("Startup class not found: "),
+        missingStartupField -> builder.append("Startup field not found: "),
+        missingStartupMethod -> builder.append("Startup method not found: "));
+    builder.append(missingStartupItem.toSourceString());
+  }
+
+  public static class Builder {
+
+    private final DexDefinitionSupplier definitions;
+    private final Set<DexReference> missingStartupItems = Sets.newIdentityHashSet();
+
+    private Origin origin;
+
+    public Builder(DexDefinitionSupplier definitions) {
+      this.definitions = definitions;
+    }
+
+    public static Builder nop() {
+      return new Builder(null);
+    }
+
+    public boolean hasMissingStartupItems() {
+      return !missingStartupItems.isEmpty();
+    }
+
+    public boolean registerStartupClass(StartupClass startupClass) {
+      if (definitions != null && definitions.definitionFor(startupClass.getReference()) == null) {
+        missingStartupItems.add(startupClass.getReference());
+        return true;
+      }
+      return false;
+    }
+
+    public boolean registerStartupMethod(StartupMethod startupMethod) {
+      if (definitions != null && definitions.definitionFor(startupMethod.getReference()) == null) {
+        missingStartupItems.add(startupMethod.getReference());
+        return true;
+      }
+      return false;
+    }
+
+    public boolean registerSyntheticStartupMethod(SyntheticStartupMethod syntheticStartupMethod) {
+      if (definitions != null
+          && definitions.definitionFor(syntheticStartupMethod.getSyntheticContextType()) == null) {
+        missingStartupItems.add(syntheticStartupMethod.getSyntheticContextType());
+        return true;
+      }
+      return false;
+    }
+
+    public Builder setOrigin(Origin origin) {
+      this.origin = origin;
+      return this;
+    }
+
+    public MissingStartupProfileItemsDiagnostic build() {
+      assert hasMissingStartupItems();
+      List<DexReference> sortedMissingStartupItems = new ArrayList<>(missingStartupItems);
+      sortedMissingStartupItems.sort(DexReference::compareTo);
+      return new MissingStartupProfileItemsDiagnostic(sortedMissingStartupItems, origin);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java b/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java
index e5af39a..fbe55a4 100644
--- a/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java
+++ b/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.cf.code.CfCheckCast;
-import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfFrame;
 import com.android.tools.r8.cf.code.CfGoto;
@@ -162,7 +161,7 @@
                       dexItemFactory.createType(
                           "Lcom/android/tools/r8/startup/InstrumentationServerImpl;")),
                   dexItemFactory.createString("getInstance")))
-          .setCode(method -> createCfCode5_getInstance(dexItemFactory, method))
+          .setCode(method -> createCfCode4_getInstance(dexItemFactory, method))
           .build(),
       DexEncodedMethod.syntheticBuilder()
           .setAccessFlags(MethodAccessFlags.fromCfAccessFlags(9, false))
@@ -176,23 +175,8 @@
                   dexItemFactory.createProto(
                       dexItemFactory.createType("V"),
                       dexItemFactory.createType("Ljava/lang/String;")),
-                  dexItemFactory.createString("addNonSyntheticMethod")))
-          .setCode(method -> createCfCode3_addNonSyntheticMethod(dexItemFactory, method))
-          .build(),
-      DexEncodedMethod.syntheticBuilder()
-          .setAccessFlags(MethodAccessFlags.fromCfAccessFlags(9, false))
-          .setApiLevelForCode(ComputedApiLevel.unknown())
-          .setApiLevelForDefinition(ComputedApiLevel.unknown())
-          .setClassFileVersion(CfVersion.V1_8)
-          .setMethod(
-              dexItemFactory.createMethod(
-                  dexItemFactory.createType(
-                      "Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
-                  dexItemFactory.createProto(
-                      dexItemFactory.createType("V"),
-                      dexItemFactory.createType("Ljava/lang/String;")),
-                  dexItemFactory.createString("addSyntheticMethod")))
-          .setCode(method -> createCfCode4_addSyntheticMethod(dexItemFactory, method))
+                  dexItemFactory.createString("addMethod")))
+          .setCode(method -> createCfCode3_addMethod(dexItemFactory, method))
           .build(),
       DexEncodedMethod.syntheticBuilder()
           .setAccessFlags(MethodAccessFlags.fromCfAccessFlags(2, false))
@@ -222,7 +206,7 @@
                       dexItemFactory.createType("V"),
                       dexItemFactory.createType("Ljava/lang/String;")),
                   dexItemFactory.createString("writeToLogcat")))
-          .setCode(method -> createCfCode7_writeToLogcat(dexItemFactory, method))
+          .setCode(method -> createCfCode6_writeToLogcat(dexItemFactory, method))
           .build(),
       DexEncodedMethod.syntheticBuilder()
           .setAccessFlags(MethodAccessFlags.fromCfAccessFlags(8, true))
@@ -254,7 +238,7 @@
                   dexItemFactory.createProto(
                       dexItemFactory.createType("V"), dexItemFactory.createType("Ljava/io/File;")),
                   dexItemFactory.createString("writeToFile")))
-          .setCode(method -> createCfCode6_writeToFile(dexItemFactory, method))
+          .setCode(method -> createCfCode5_writeToFile(dexItemFactory, method))
           .build()
     };
   }
@@ -460,8 +444,7 @@
         ImmutableList.of());
   }
 
-  public static CfCode createCfCode3_addNonSyntheticMethod(
-      DexItemFactory factory, DexMethod method) {
+  public static CfCode createCfCode3_addMethod(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
     CfLabel label2 = new CfLabel();
@@ -495,72 +478,7 @@
         ImmutableList.of());
   }
 
-  public static CfCode createCfCode4_addSyntheticMethod(DexItemFactory factory, DexMethod method) {
-    CfLabel label0 = new CfLabel();
-    CfLabel label1 = new CfLabel();
-    CfLabel label2 = new CfLabel();
-    return new CfCode(
-        method.holder,
-        3,
-        1,
-        ImmutableList.of(
-            label0,
-            new CfInvoke(
-                184,
-                factory.createMethod(
-                    factory.createType("Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
-                    factory.createProto(
-                        factory.createType(
-                            "Lcom/android/tools/r8/startup/InstrumentationServerImpl;")),
-                    factory.createString("getInstance")),
-                false),
-            new CfNew(factory.stringBuilderType),
-            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
-            new CfInvoke(
-                183,
-                factory.createMethod(
-                    factory.stringBuilderType,
-                    factory.createProto(factory.voidType),
-                    factory.createString("<init>")),
-                false),
-            new CfConstNumber(83, ValueType.INT),
-            new CfInvoke(
-                182,
-                factory.createMethod(
-                    factory.stringBuilderType,
-                    factory.createProto(factory.stringBuilderType, factory.charType),
-                    factory.createString("append")),
-                false),
-            new CfLoad(ValueType.OBJECT, 0),
-            new CfInvoke(
-                182,
-                factory.createMethod(
-                    factory.stringBuilderType,
-                    factory.createProto(factory.stringBuilderType, factory.stringType),
-                    factory.createString("append")),
-                false),
-            new CfInvoke(
-                182,
-                factory.createMethod(
-                    factory.stringBuilderType,
-                    factory.createProto(factory.stringType),
-                    factory.createString("toString")),
-                false),
-            new CfInvoke(
-                183,
-                factory.createMethod(
-                    factory.createType("Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
-                    factory.createProto(factory.voidType, factory.stringType),
-                    factory.createString("addLine")),
-                false),
-            label1,
-            new CfReturnVoid(),
-            label2),
-        ImmutableList.of(),
-        ImmutableList.of());
-  }
-
-  public static CfCode createCfCode5_getInstance(DexItemFactory factory, DexMethod method) {
+  public static CfCode createCfCode4_getInstance(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     return new CfCode(
         method.holder,
@@ -578,7 +496,7 @@
         ImmutableList.of());
   }
 
-  public static CfCode createCfCode6_writeToFile(DexItemFactory factory, DexMethod method) {
+  public static CfCode createCfCode5_writeToFile(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
     CfLabel label2 = new CfLabel();
@@ -805,7 +723,7 @@
         ImmutableList.of());
   }
 
-  public static CfCode createCfCode7_writeToLogcat(DexItemFactory factory, DexMethod method) {
+  public static CfCode createCfCode6_writeToLogcat(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
     CfLabel label2 = new CfLabel();
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 2de90be..e1ac8ba 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -38,7 +38,7 @@
       generator.forFixedClass("$EnumUnboxingLocalUtility");
   public final SyntheticKind ENUM_UNBOXING_SHARED_UTILITY_CLASS =
       generator.forFixedClass("$EnumUnboxingSharedUtility");
-  public final SyntheticKind COMPANION_CLASS = generator.forFixedClass("$-CC");
+  public final SyntheticKind COMPANION_CLASS = generator.forFixedClass(COMPANION_CLASS_SUFFIX);
   public final SyntheticKind EMULATED_INTERFACE_CLASS =
       generator.forFixedClass(InterfaceDesugaringForTesting.EMULATED_INTERFACE_CLASS_SUFFIX);
   public final SyntheticKind RETARGET_CLASS = generator.forFixedClass("RetargetClass");
@@ -348,6 +348,7 @@
     }
   }
 
+  public static final String COMPANION_CLASS_SUFFIX = "$-CC";
   private static final String SYNTHETIC_CLASS_SEPARATOR = "$$";
   /**
    * The internal synthetic class separator is only used for representing synthetic items during
@@ -360,7 +361,7 @@
    * The external synthetic class separator is used when writing classes. It may appear in types
    * during compilation as the output of a compilation may be the input to another.
    */
-  private static final String EXTERNAL_SYNTHETIC_CLASS_SEPARATOR =
+  public static final String EXTERNAL_SYNTHETIC_CLASS_SEPARATOR =
       SYNTHETIC_CLASS_SEPARATOR + "ExternalSynthetic";
   /** Method name when generating synthetic methods in a class. */
   static final String INTERNAL_SYNTHETIC_METHOD_NAME = "m";
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 14b4d67..679e3c7 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -35,6 +35,7 @@
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.startup.StartupOrder;
+import com.android.tools.r8.experimental.startup.StartupProfileProviderUtils;
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.features.FeatureSplitConfiguration;
 import com.android.tools.r8.graph.DexType;
@@ -42,6 +43,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.shaking.FilteredClassPath;
+import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -524,6 +526,9 @@
             StringUtils.joinLines(dumpOptions.getMainDexKeepRules()).getBytes(),
             ZipEntry.DEFLATED);
       }
+      if (dumpOptions.hasStartupProfileProviders()) {
+        dumpStartupProfileProviders(dumpOptions.getStartupProfileProviders(), options, out);
+      }
       nextDexIndex =
           dumpProgramResources(
               dumpProgramFileName,
@@ -553,6 +558,24 @@
     return nextDexIndex;
   }
 
+  private void dumpStartupProfileProviders(
+      Collection<StartupProfileProvider> startupProfileProviders,
+      InternalOptions options,
+      ZipOutputStream out)
+      throws IOException {
+    int startupProfileProviderIndex = 1;
+    for (StartupProfileProvider startupProfileProvider : startupProfileProviders) {
+      String startupProfileProviderFileName =
+          "startup-profile-" + startupProfileProviderIndex + ".txt";
+      writeToZipStream(
+          out,
+          startupProfileProviderFileName,
+          StartupProfileProviderUtils.serializeToString(options, startupProfileProvider).getBytes(),
+          ZipEntry.DEFLATED);
+      startupProfileProviderIndex++;
+    }
+  }
+
   private static ClassFileResourceProvider createClassFileResourceProvider(
       Map<String, ProgramResource> classPathResources) {
     return new ClassFileResourceProvider() {
diff --git a/src/main/java/com/android/tools/r8/utils/ClassReferenceUtils.java b/src/main/java/com/android/tools/r8/utils/ClassReferenceUtils.java
index 30754f0..072022d 100644
--- a/src/main/java/com/android/tools/r8/utils/ClassReferenceUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ClassReferenceUtils.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
 import java.util.Comparator;
 
 public class ClassReferenceUtils {
@@ -36,6 +37,14 @@
     return COMPARATOR;
   }
 
+  public static ClassReference parseClassDescriptor(String classDescriptor) {
+    if (DescriptorUtils.isClassDescriptor(classDescriptor)) {
+      return Reference.classFromDescriptor(classDescriptor);
+    } else {
+      return null;
+    }
+  }
+
   public static DexType toDexType(ClassReference classReference, DexItemFactory dexItemFactory) {
     return dexItemFactory.createType(classReference.getDescriptor());
   }
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
index 3c2605a..b30c961 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -18,12 +18,14 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.function.Consumer;
+import java.util.function.Function;
 
 /**
  * Wrapper to make it easy to call R8 in compat mode when compiling a dump file.
@@ -58,7 +60,8 @@
           "--pg-conf",
           "--pg-map-output",
           "--desugared-lib",
-          "--threads");
+          "--threads",
+          "--startup-profile");
 
   private static final List<String> VALID_OPTIONS_WITH_TWO_OPERANDS =
       Arrays.asList("--feature-jar");
@@ -84,6 +87,7 @@
     List<Path> classpath = new ArrayList<>();
     List<Path> config = new ArrayList<>();
     List<Path> mainDexRulesFiles = new ArrayList<>();
+    List<Path> startupProfileFiles = new ArrayList<>();
     int minApi = 1;
     int threads = -1;
     boolean enableMissingLibraryApiModeling = false;
@@ -169,6 +173,11 @@
               mainDexRulesFiles.add(Paths.get(operand));
               break;
             }
+          case "--startup-profile":
+            {
+              startupProfileFiles.add(Paths.get(operand));
+              break;
+            }
           default:
             throw new IllegalArgumentException("Unimplemented option: " + option);
         }
@@ -208,6 +217,8 @@
         .accept(new Object[] {enableMissingLibraryApiModeling});
     getReflectiveBuilderMethod(commandBuilder, "setAndroidPlatformBuild", boolean.class)
         .accept(new Object[] {androidPlatformBuild});
+    getReflectiveBuilderMethod(commandBuilder, "addStartupProfileProviders", Collection.class)
+        .accept(new Object[] {createStartupProfileProviders(startupProfileFiles)});
     if (desugaredLibJson != null) {
       commandBuilder.addDesugaredLibraryConfiguration(readAllBytesJava7(desugaredLibJson));
     }
@@ -238,6 +249,17 @@
     }
   }
 
+  private static Collection<?> createStartupProfileProviders(List<Path> startupProfileFiles) {
+    List<Object> startupProfileProviders = new ArrayList<>();
+    for (Path startupProfileFile : startupProfileFiles) {
+      callReflectiveUtilsMethod(
+          "createStartupProfileProviderFromDumpFile",
+          new Class<?>[] {Path.class},
+          fn -> startupProfileProviders.add(fn.apply(new Object[] {startupProfileFile})));
+    }
+    return startupProfileProviders;
+  }
+
   private static Consumer<Object[]> getReflectiveBuilderMethod(
       Builder builder, String setter, Class<?>... parameters) {
     try {
@@ -256,6 +278,32 @@
     }
   }
 
+  private static void callReflectiveUtilsMethod(
+      String methodName, Class<?>[] parameters, Consumer<Function<Object[], Object>> fnConsumer) {
+    Class<?> utilsClass;
+    try {
+      utilsClass = Class.forName("com.android.tools.r8.utils.CompileDumpUtils");
+    } catch (ClassNotFoundException e) {
+      return;
+    }
+
+    Method declaredMethod;
+    try {
+      declaredMethod = utilsClass.getMethod(methodName, parameters);
+    } catch (NoSuchMethodException e) {
+      return;
+    }
+
+    fnConsumer.accept(
+        args -> {
+          try {
+            return declaredMethod.invoke(null, args);
+          } catch (Exception e) {
+            throw new RuntimeException(e);
+          }
+        });
+  }
+
   // We cannot use StringResource since this class is added to the class path and has access only
   // to the public APIs.
   private static String readAllBytesJava7(Path filePath) {
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpD8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpD8.java
index b2c4269..32892d7 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpD8.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpD8.java
@@ -15,10 +15,12 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 /**
  * Wrapper to make it easy to call D8 mode when compiling a dump file.
@@ -50,7 +52,8 @@
           "--main-dex-list",
           "--main-dex-list-output",
           "--desugared-lib",
-          "--threads");
+          "--threads",
+          "--startup-profile");
 
   public static void main(String[] args) throws CompilationFailedException {
     OutputMode outputMode = OutputMode.DexIndexed;
@@ -61,6 +64,7 @@
     List<Path> library = new ArrayList<>();
     List<Path> classpath = new ArrayList<>();
     List<Path> mainDexRulesFiles = new ArrayList<>();
+    List<Path> startupProfileFiles = new ArrayList<>();
     int minApi = 1;
     int threads = -1;
     boolean enableMissingLibraryApiModeling = false;
@@ -131,6 +135,11 @@
               mainDexRulesFiles.add(Paths.get(operand));
               break;
             }
+          case "--startup-profile":
+            {
+              startupProfileFiles.add(Paths.get(operand));
+              break;
+            }
           default:
             throw new IllegalArgumentException("Unimplemented option: " + option);
         }
@@ -151,6 +160,13 @@
         .accept(new Object[] {enableMissingLibraryApiModeling});
     getReflectiveBuilderMethod(commandBuilder, "setAndroidPlatformBuild", boolean.class)
         .accept(new Object[] {androidPlatformBuild});
+    getReflectiveBuilderMethod(commandBuilder, "addStartupProfileProviders", Collection.class)
+        .accept(
+            new Object[] {
+              startupProfileFiles.stream()
+                  .map(CompileDumpUtils::createStartupProfileProviderFromDumpFile)
+                  .collect(Collectors.toList())
+            });
     if (desugaredLibJson != null) {
       commandBuilder.addDesugaredLibraryConfiguration(readAllBytesJava7(desugaredLibJson));
     }
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpUtils.java b/src/main/java/com/android/tools/r8/utils/CompileDumpUtils.java
new file mode 100644
index 0000000..14acc11
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpUtils.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.utils;
+
+import com.android.tools.r8.KeepMethodForCompileDump;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.startup.StartupProfileBuilder;
+import com.android.tools.r8.startup.StartupProfileProvider;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+class CompileDumpUtils {
+
+  @KeepMethodForCompileDump
+  static StartupProfileProvider createStartupProfileProviderFromDumpFile(Path path) {
+    return new StartupProfileProvider() {
+
+      @Override
+      public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) {
+        try {
+          try (BufferedReader bufferedReader = Files.newBufferedReader(path)) {
+            while (bufferedReader.ready()) {
+              String rule = bufferedReader.readLine();
+              if (rule.charAt(0) == 'S') {
+                String classDescriptor = rule.substring(1);
+                assert DescriptorUtils.isClassDescriptor(classDescriptor);
+                startupProfileBuilder.addSyntheticStartupMethod(
+                    syntheticStartupMethodBuilder ->
+                        syntheticStartupMethodBuilder.setSyntheticContextReference(
+                            Reference.classFromDescriptor(classDescriptor)));
+              } else {
+                MethodReference methodReference = MethodReferenceUtils.parseSmaliString(rule);
+                if (methodReference != null) {
+                  startupProfileBuilder.addStartupMethod(
+                      startupMethodBuilder ->
+                          startupMethodBuilder.setMethodReference(methodReference));
+                } else {
+                  assert DescriptorUtils.isClassDescriptor(rule);
+                  startupProfileBuilder.addStartupClass(
+                      startupClassBuilder ->
+                          startupClassBuilder.setClassReference(
+                              Reference.classFromDescriptor(rule)));
+                }
+              }
+            }
+          }
+        } catch (IOException e) {
+          throw new UncheckedIOException(e);
+        }
+      }
+
+      @Override
+      public Origin getOrigin() {
+        return new PathOrigin(path);
+      }
+    };
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index 7cce06b..99645fa 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -404,6 +404,17 @@
   }
 
   /**
+   * Get package java name from a class type name.
+   *
+   * @param typeName a class descriptor i.e. "java.lang.Object"
+   * @return java package name i.e. "java.lang"
+   */
+  public static String getPackageNameFromTypeName(String typeName) {
+    return getPackageNameFromBinaryName(
+        getClassBinaryNameFromDescriptor(javaTypeToDescriptor(typeName)));
+  }
+
+  /**
    * Convert package name to a binary name.
    *
    * @param packageName a package name i.e., "java.lang"
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 f02405c..07322dc 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
-import static com.android.tools.r8.utils.AndroidApiLevel.ANDROID_PLATFORM;
 import static com.android.tools.r8.utils.AndroidApiLevel.B;
 import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault;
 
@@ -269,13 +268,15 @@
   }
 
   public void configureAndroidPlatformBuild(boolean isAndroidPlatformBuild) {
+    assert !androidPlatformBuild;
     if (!isAndroidPlatformBuild) {
       return;
     }
+    androidPlatformBuild = isAndroidPlatformBuild;
     // Configure options according to platform build assumptions.
     // See go/r8platformflag and b/232073181.
-    minApiLevel = ANDROID_PLATFORM;
     apiModelingOptions().disableMissingApiModeling();
+    enableBackportMethods = false;
   }
 
   public boolean printTimes = System.getProperty("com.android.tools.r8.printtimes") != null;
@@ -465,6 +466,9 @@
     if (tool == Tool.R8) {
       marker.setR8Mode(forceProguardCompatibility ? "compatibility" : "full");
     }
+    if (androidPlatformBuild) {
+      marker.setAndroidPlatformBuild();
+    }
     return marker;
   }
 
@@ -500,10 +504,6 @@
     this.globalSyntheticsConsumer = globalSyntheticsConsumer;
   }
 
-  public boolean isAndroidPlatform() {
-    return minApiLevel == ANDROID_PLATFORM;
-  }
-
   public boolean isDesugaredLibraryCompilation() {
     return machineDesugaredLibrarySpecification.isLibraryCompilation();
   }
@@ -512,10 +512,6 @@
     return relocatorCompilation;
   }
 
-  public boolean shouldBackportMethods() {
-    return !hasConsumer() || isGeneratingDex() || isCfDesugaring();
-  }
-
   public boolean shouldKeepStackMapTable() {
     assert isRelocatorCompilation() || getProguardConfiguration() != null;
     return isRelocatorCompilation() || getProguardConfiguration().getKeepAttributes().stackMapTable;
@@ -600,6 +596,7 @@
   // Skipping min_api check and compiling an intermediate result intended for later merging.
   // Intermediate builds also emits or update synthesized classes mapping.
   public boolean intermediate = false;
+  private boolean androidPlatformBuild = false;
   public boolean retainCompileTimeAnnotations = true;
   public boolean ignoreBootClasspathEnumsForMaindexTracing =
       System.getProperty("com.android.tools.r8.ignoreBootClasspathEnumsForMaindexTracing") != null;
@@ -611,6 +608,8 @@
   public boolean enableLoadStoreOptimization = true;
   // Flag to turn on/off desugaring in D8/R8.
   public DesugarState desugarState = DesugarState.ON;
+  // Flag to turn on/off backport methods.
+  public boolean enableBackportMethods = true;
   // Flag to turn on/off reduction of nest to improve class merging optimizations.
   public boolean enableNestReduction = true;
   // Defines interface method rewriter behavior.
@@ -816,7 +815,7 @@
       new KotlinOptimizationOptions();
   private final ApiModelTestingOptions apiModelTestingOptions = new ApiModelTestingOptions();
   private final DesugarSpecificOptions desugarSpecificOptions = new DesugarSpecificOptions();
-  private final StartupOptions startupOptions = new StartupOptions();
+  private final StartupOptions startupOptions = new StartupOptions(this);
   private final StartupInstrumentationOptions startupInstrumentationOptions =
       new StartupInstrumentationOptions();
   public final TestingOptions testing = new TestingOptions();
@@ -2205,7 +2204,10 @@
     // the highest known API level when the compiler is built. This ensures that when this is used
     // by the Android Platform build (which normally use an API level of 10000) there will be
     // no rewriting of backported methods. See b/147480264.
-    return desugarState.isOn() && getMinApiLevel().isLessThanOrEqualTo(AndroidApiLevel.LATEST);
+    return enableBackportMethods
+        && desugarState.isOn()
+        // TODO(b/232073181): This platform check should rather be controlled via the platform flag.
+        && getMinApiLevel().isLessThanOrEqualTo(AndroidApiLevel.LATEST);
   }
 
   public boolean enableTryWithResourcesDesugaring() {
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 8e240d7..251edcc 100644
--- a/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
@@ -16,9 +16,11 @@
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
 import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Iterator;
+import java.util.List;
 
 public class MethodReferenceUtils {
 
@@ -106,6 +108,46 @@
     }
   }
 
+  public static MethodReference parseSmaliString(String classAndMethodDescriptor) {
+    int arrowStartIndex = classAndMethodDescriptor.indexOf("->");
+    if (arrowStartIndex >= 0) {
+      return parseSmaliString(classAndMethodDescriptor, arrowStartIndex);
+    }
+    return null;
+  }
+
+  public static MethodReference parseSmaliString(
+      String classAndMethodDescriptor, int arrowStartIndex) {
+    String classDescriptor = classAndMethodDescriptor.substring(0, arrowStartIndex);
+    ClassReference methodHolder = ClassReferenceUtils.parseClassDescriptor(classDescriptor);
+    if (methodHolder == null) {
+      return null;
+    }
+
+    int methodNameStartIndex = arrowStartIndex + 2;
+    String protoWithNameDescriptor = classAndMethodDescriptor.substring(methodNameStartIndex);
+    int methodNameEndIndex = protoWithNameDescriptor.indexOf('(');
+    if (methodNameEndIndex <= 0) {
+      return null;
+    }
+    String methodName = protoWithNameDescriptor.substring(0, methodNameEndIndex);
+
+    String protoDescriptor = protoWithNameDescriptor.substring(methodNameEndIndex);
+    return parseMethodProto(methodHolder, methodName, protoDescriptor);
+  }
+
+  private static MethodReference parseMethodProto(
+      ClassReference methodHolder, String methodName, String protoDescriptor) {
+    List<TypeReference> parameterTypes = new ArrayList<>();
+    for (String parameterTypeDescriptor :
+        DescriptorUtils.getArgumentTypeDescriptors(protoDescriptor)) {
+      parameterTypes.add(Reference.typeFromDescriptor(parameterTypeDescriptor));
+    }
+    String returnTypeDescriptor = DescriptorUtils.getReturnTypeDescriptor(protoDescriptor);
+    TypeReference returnType = Reference.returnTypeFromDescriptor(returnTypeDescriptor);
+    return Reference.method(methodHolder, methodName, parameterTypes, returnType);
+  }
+
   public static DexMethod toDexMethod(
       MethodReference methodReference, DexItemFactory dexItemFactory) {
     return dexItemFactory.createMethod(
@@ -115,6 +157,13 @@
         methodReference.getMethodName());
   }
 
+  public static String toSmaliString(MethodReference methodReference) {
+    return methodReference.getHolderClass().getDescriptor()
+        + "->"
+        + methodReference.getMethodName()
+        + methodReference.getMethodDescriptor();
+  }
+
   public static String toSourceStringWithoutHolderAndReturnType(MethodReference methodReference) {
     return toSourceString(methodReference, false, false);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
index 920b83c..aca16a9 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
@@ -3,14 +3,17 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.ClassConflictResolver;
 import com.android.tools.r8.dex.ApplicationReader.ProgramClassConflictResolver;
 import com.android.tools.r8.errors.DuplicateTypesDiagnostic;
 import com.android.tools.r8.graph.ClassKind;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.InternalGlobalSyntheticsProgramProvider.GlobalsEntryOrigin;
 import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Supplier;
@@ -61,7 +64,31 @@
   public static ProgramClassConflictResolver defaultConflictResolver(Reporter reporter) {
     // The default conflict resolver only merges synthetic classes generated by D8 correctly.
     // All other conflicts are reported as a fatal error.
-    return (DexProgramClass a, DexProgramClass b) -> mergeClasses(reporter, a, b);
+    return wrappedConflictResolver(null, reporter);
+  }
+
+  public static ProgramClassConflictResolver wrappedConflictResolver(
+      ClassConflictResolver clientResolver, Reporter reporter) {
+    return (a, b) -> {
+      DexProgramClass clazz = mergeClasses(a, b);
+      if (clazz != null) {
+        return clazz;
+      }
+      if (clientResolver != null) {
+        List<Origin> origins = new ArrayList<>();
+        origins.add(a.getOrigin());
+        origins.add(b.getOrigin());
+        Origin origin =
+            clientResolver.resolveDuplicateClass(a.getClassReference(), origins, reporter);
+        if (origin == a.getOrigin()) {
+          return a;
+        }
+        if (origin == b.getOrigin()) {
+          return b;
+        }
+      }
+      throw reportDuplicateTypes(reporter, a, b);
+    };
   }
 
   private static RuntimeException reportDuplicateTypes(
@@ -72,38 +99,36 @@
             ImmutableList.of(a.getOrigin(), b.getOrigin())));
   }
 
-  private static DexProgramClass mergeClasses(
-      Reporter reporter, DexProgramClass a, DexProgramClass b) {
+  private static DexProgramClass mergeClasses(DexProgramClass a, DexProgramClass b) {
     assert a.type == b.type;
     boolean syntheticA = a.accessFlags.isSynthetic();
     boolean syntheticB = b.accessFlags.isSynthetic();
     if (syntheticA && syntheticB) {
-      return mergeIfLegacySynthetics(reporter, a, b);
+      return mergeIfLegacySynthetics(a, b);
     } else if (syntheticA) {
-      return mergeIfGlobalSynthetic(reporter, a, b);
+      return mergeIfGlobalSynthetic(a, b);
     } else if (syntheticB) {
-      return mergeIfGlobalSynthetic(reporter, b, a);
+      return mergeIfGlobalSynthetic(b, a);
     }
-    throw reportDuplicateTypes(reporter, a, b);
+    return null;
   }
 
   private static DexProgramClass mergeIfGlobalSynthetic(
-      Reporter reporter, DexProgramClass synthetic, DexProgramClass nonSynthetic) {
+      DexProgramClass synthetic, DexProgramClass nonSynthetic) {
     assert synthetic.accessFlags.isSynthetic();
     assert !nonSynthetic.accessFlags.isSynthetic();
     if (synthetic.getOrigin() instanceof GlobalsEntryOrigin) {
       return nonSynthetic;
     }
-    throw reportDuplicateTypes(reporter, nonSynthetic, synthetic);
+    return null;
   }
 
-  private static DexProgramClass mergeIfLegacySynthetics(
-      Reporter reporter, DexProgramClass a, DexProgramClass b) {
+  private static DexProgramClass mergeIfLegacySynthetics(DexProgramClass a, DexProgramClass b) {
     if (a.type.isLegacySynthesizedTypeAllowedDuplication()) {
       assert assertEqualClasses(a, b);
       return a;
     }
-    throw reportDuplicateTypes(reporter, a, b);
+    return null;
   }
 
   private static boolean assertEqualClasses(DexProgramClass a, DexProgramClass b) {
diff --git a/src/main/java/com/android/tools/r8/utils/UTF8TextInputStream.java b/src/main/java/com/android/tools/r8/utils/UTF8TextInputStream.java
new file mode 100644
index 0000000..20252e7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/UTF8TextInputStream.java
@@ -0,0 +1,36 @@
+// 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.utils;
+
+import com.android.tools.r8.TextInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class UTF8TextInputStream implements TextInputStream {
+
+  private final InputStream inputStream;
+
+  public UTF8TextInputStream(Path path) throws IOException {
+    this(Files.newInputStream(path));
+  }
+
+  public UTF8TextInputStream(InputStream inputStream) {
+    this.inputStream = inputStream;
+  }
+
+  @Override
+  public InputStream getInputStream() {
+    return inputStream;
+  }
+
+  @Override
+  public Charset getCharset() {
+    return StandardCharsets.UTF_8;
+  }
+}
diff --git a/src/main/keep.txt b/src/main/keep.txt
index ced5b4a..689e5b5 100644
--- a/src/main/keep.txt
+++ b/src/main/keep.txt
@@ -5,6 +5,7 @@
 # TODO(b/204058761): Remove when we can use test proguard options.
 -keep @com.android.tools.r8.Keep class * { public *; }
 -keep @com.android.tools.r8.KeepForSubclassing class * { public *; protected *; }
+-keepclasseswithmembers class * { @com.android.tools.r8.KeepMethodForCompileDump <methods>; }
 
 # Keep all things that can be reached from the retrace api and keep the annotation
 -keep @com.android.tools.r8.KeepForRetraceApi class * { public *; }
diff --git a/src/test/examplesJava9/collectionof/CollectionOfMain.java b/src/test/examplesJava9/collectionof/CollectionOfMain.java
new file mode 100644
index 0000000..fc8384e
--- /dev/null
+++ b/src/test/examplesJava9/collectionof/CollectionOfMain.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package collectionof;
+
+import java.util.List;
+import java.util.Set;
+
+public class CollectionOfMain {
+
+  public static void main(String[] args) {
+    try {
+      System.out.println(Set.of("one").contains(null));
+    } catch (NullPointerException npe) {
+      System.out.println("npe");
+    }
+    try {
+      System.out.println(List.of("one").contains(null));
+    } catch (NullPointerException npe) {
+      System.out.println("npe");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/BackportedMethodListTest.java b/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
index dcf816e..b8a76cc 100644
--- a/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
+++ b/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
@@ -98,8 +98,10 @@
         backports.contains("java/lang/StrictMath#multiplyExact(JI)J"));
     // Java 9, 10 and 11 method added at API level S.
     // The method is not backported in desugared library JDK 11 (already present).
+    // TODO(b/243679691): Should no use backport but retargeting in between 24 and 33,
     assertEquals(
-        apiLevel < AndroidApiLevel.S.getLevel(),
+        apiLevel < AndroidApiLevel.S.getLevel()
+            && (mode != Mode.LIBRARY_DESUGAR_11 || apiLevel >= AndroidApiLevel.N.getLevel()),
         backports.contains("java/util/List#copyOf(Ljava/util/Collection;)Ljava/util/List;"));
 
     // Java 9, 10 and 11 methods not yet added.
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index aebb5a6..e33cb93 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -767,6 +767,12 @@
     numThreadsOptionInvalid("two");
   }
 
+  @Test
+  public void androidPlatformBuildFlag() throws Exception {
+    assertFalse(parse().getAndroidPlatformBuild());
+    assertTrue(parse("--android-platform-build").getAndroidPlatformBuild());
+  }
+
   @Override
   String[] requiredArgsForTest() {
     return new String[0];
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index d14b01f..8f465ff 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.benchmarks.BenchmarkResults;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import java.nio.file.Path;
@@ -30,6 +31,16 @@
   private StringBuilder proguardMapOutputBuilder = null;
 
   @Override
+  public boolean isD8TestBuilder() {
+    return true;
+  }
+
+  @Override
+  public D8TestBuilder asD8TestBuilder() {
+    return this;
+  }
+
+  @Override
   D8TestBuilder self() {
     return this;
   }
@@ -118,4 +129,16 @@
     getBuilder().setProguardMapConsumer((s, h) -> proguardMapOutputBuilder.append(s));
     return self();
   }
+
+  public D8TestBuilder addStartupProfileProviders(
+      StartupProfileProvider... startupProfileProviders) {
+    builder.addStartupProfileProviders(startupProfileProviders);
+    return self();
+  }
+
+  public D8TestBuilder addStartupProfileProviders(
+      Collection<StartupProfileProvider> startupProfileProviders) {
+    builder.addStartupProfileProviders(startupProfileProviders);
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/KotlinTestParameters.java b/src/test/java/com/android/tools/r8/KotlinTestParameters.java
index abce6c3..0002260 100644
--- a/src/test/java/com/android/tools/r8/KotlinTestParameters.java
+++ b/src/test/java/com/android/tools/r8/KotlinTestParameters.java
@@ -44,6 +44,10 @@
     return kotlinc.is(compilerVersion);
   }
 
+  public boolean isKotlinDev() {
+    return kotlinc.is(KotlinCompilerVersion.KOTLIN_DEV);
+  }
+
   public boolean is(KotlinCompilerVersion compilerVersion, KotlinTargetVersion targetVersion) {
     return is(compilerVersion) && this.targetVersion == targetVersion;
   }
diff --git a/src/test/java/com/android/tools/r8/MarkerMatcher.java b/src/test/java/com/android/tools/r8/MarkerMatcher.java
index 22610d3..bbd1952 100644
--- a/src/test/java/com/android/tools/r8/MarkerMatcher.java
+++ b/src/test/java/com/android/tools/r8/MarkerMatcher.java
@@ -95,6 +95,20 @@
     };
   }
 
+  public static Matcher<Marker> markerAndroidPlatformBuild() {
+    return new MarkerMatcher() {
+      @Override
+      protected boolean eval(Marker marker) {
+        return marker.isAndroidPlatformBuild();
+      }
+
+      @Override
+      protected void explain(Description description) {
+        description.appendText("platform");
+      }
+    };
+  }
+
   public static Matcher<Marker> markerCompilationMode(CompilationMode compilationMode) {
     return new MarkerMatcher() {
       @Override
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 8b951c9..1c7a5ea 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -910,6 +910,12 @@
     assertTrue(options.enableStubbingOfClasses);
   }
 
+  @Test
+  public void androidPlatformBuildFlag() throws Exception {
+    assertFalse(parse().getAndroidPlatformBuild());
+    assertTrue(parse("--android-platform-build").getAndroidPlatformBuild());
+  }
+
   @Override
   String[] requiredArgsForTest() {
     return new String[0];
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 824203a..53ff395 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.shaking.NoVerticalClassMergingRule;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -72,6 +73,16 @@
   private boolean createDefaultProguardMapConsumer = true;
 
   @Override
+  public boolean isR8TestBuilder() {
+    return true;
+  }
+
+  @Override
+  public R8TestBuilder<?> asR8TestBuilder() {
+    return this;
+  }
+
+  @Override
   R8TestCompileResult internalCompile(
       Builder builder,
       Consumer<InternalOptions> optionsConsumer,
@@ -766,4 +777,14 @@
     createDefaultProguardMapConsumer = false;
     return self();
   }
+
+  public T addStartupProfileProviders(StartupProfileProvider... startupProfileProviders) {
+    builder.addStartupProfileProviders(startupProfileProviders);
+    return self();
+  }
+
+  public T addStartupProfileProviders(Collection<StartupProfileProvider> startupProfileProviders) {
+    builder.addStartupProfileProviders(startupProfileProviders);
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 5d9d7f5..141f879 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -91,6 +91,22 @@
   LibraryDesugaringTestConfiguration libraryDesugaringTestConfiguration =
       LibraryDesugaringTestConfiguration.DISABLED;
 
+  public boolean isD8TestBuilder() {
+    return false;
+  }
+
+  public D8TestBuilder asD8TestBuilder() {
+    return null;
+  }
+
+  public boolean isR8TestBuilder() {
+    return false;
+  }
+
+  public R8TestBuilder<?> asR8TestBuilder() {
+    return null;
+  }
+
   public boolean isTestShrinkerBuilder() {
     return false;
   }
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
index c53ec8c..a15b915 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
@@ -126,8 +126,9 @@
   public abstract TestDiagnosticMessages assertWarningsMatch(
       Collection<Matcher<Diagnostic>> matchers);
 
-  public final TestDiagnosticMessages assertErrorsMatch(Matcher<Diagnostic> matcher) {
-    return assertErrorsMatch(Collections.singletonList(matcher));
+  @SafeVarargs
+  public final TestDiagnosticMessages assertErrorsMatch(Matcher<Diagnostic>... matchers) {
+    return assertErrorsMatch(Arrays.asList(matchers));
   }
 
   public abstract TestDiagnosticMessages assertErrorsMatch(
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index d28d969..8d78785 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -173,6 +173,14 @@
     return addDontWarn("javax.annotation.Nullable");
   }
 
+  public T addDontWarnJavaLangReflectAnnotatedType() {
+    return addDontWarn("java.lang.reflect.AnnotatedType");
+  }
+
+  public T addDontWarnJavaLangInvokeLambdaMetadataFactory() {
+    return addDontWarn("java.lang.invoke.LambdaMetafactory");
+  }
+
   public T addIgnoreWarnings() {
     return addIgnoreWarnings(true);
   }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceInitializerTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceInitializerTest.java
new file mode 100644
index 0000000..ea08c59
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceInitializerTest.java
@@ -0,0 +1,179 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.lang.reflect.Method;
+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 ApiModelOutlineInstanceInitializerTest extends TestBase {
+
+  private static final AndroidApiLevel classApiLevel = AndroidApiLevel.M;
+
+  private static final String[] EXPECTED =
+      new String[] {"LibraryClass::<clinit>", "Argument::<clinit>", "Hello World!"};
+
+  @Parameter public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    testBuilder
+        .addLibraryClasses(LibraryClass.class, Argument.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .addProgramClasses(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .addAndroidBuildVersion()
+        .apply(setMockApiLevelForClass(Argument.class, classApiLevel))
+        .apply(
+            setMockApiLevelForMethod(
+                Argument.class.getDeclaredConstructor(String.class), classApiLevel))
+        .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
+        .apply(
+            setMockApiLevelForMethod(
+                LibraryClass.class.getDeclaredConstructor(Argument.class), classApiLevel))
+        .apply(setMockApiLevelForMethod(LibraryClass.class.getMethod("print"), classApiLevel))
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+        .apply(ApiModelingTestHelper::disableStubbingOfClasses);
+  }
+
+  public boolean addToBootClasspath() {
+    return parameters.isDexRuntime()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(classApiLevel);
+  }
+
+  @Test
+  public void testD8Debug() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .inspect(inspector -> inspect(inspector, false))
+        .applyIf(
+            addToBootClasspath(),
+            b -> b.addBootClasspathClasses(LibraryClass.class, Argument.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .inspect(inspector -> inspect(inspector, false))
+        .applyIf(
+            addToBootClasspath(),
+            b -> b.addBootClasspathClasses(LibraryClass.class, Argument.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .apply(this::setupTestBuilder)
+        .addKeepMainRule(Main.class)
+        .compile()
+        .inspect(inspector -> inspect(inspector, true))
+        .applyIf(
+            addToBootClasspath(),
+            b -> b.addBootClasspathClasses(LibraryClass.class, Argument.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  private void inspect(CodeInspector inspector, boolean isR8) throws Exception {
+    Method mainMethod = Main.class.getMethod("main", String[].class);
+    verifyThat(inspector, parameters, Argument.class.getDeclaredConstructor(String.class))
+        .isOutlinedFromUntil(mainMethod, AndroidApiLevel.B);
+    verifyThat(inspector, parameters, LibraryClass.class.getDeclaredConstructor(Argument.class))
+        .isOutlinedFromUntil(mainMethod, AndroidApiLevel.B);
+    verifyThat(inspector, parameters, LibraryClass.class.getMethod("print"))
+        .isOutlinedFromUntil(mainMethod, isR8 ? AndroidApiLevel.B : classApiLevel);
+  }
+
+  private void checkOutput(SingleTestRunResult<?> runResult) {
+    if (parameters.isDexRuntime()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(classApiLevel)) {
+      runResult.assertSuccessWithOutputLines(EXPECTED);
+    } else {
+      runResult.assertSuccessWithOutputLines("Not calling API");
+    }
+  }
+
+  public static class Argument {
+
+    private final String string;
+
+    static {
+      System.out.println("Argument::<clinit>");
+    }
+
+    public Argument(String string) {
+      this.string = string;
+    }
+
+    @Override
+    public String toString() {
+      return string;
+    }
+  }
+
+  // Only present from api level 23.
+  public static class LibraryClass {
+
+    private final Argument argument;
+
+    static {
+      System.out.println("LibraryClass::<clinit>");
+    }
+
+    public LibraryClass(Argument argument) {
+      this.argument = argument;
+    }
+
+    public void print() {
+      System.out.println(argument.toString());
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      if (AndroidBuildVersion.VERSION >= 23) {
+        LibraryClass libraryClass = new LibraryClass(new Argument("Hello World!"));
+        libraryClass.print();
+      } else {
+        System.out.println("Not calling API");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
index 4166e5e..178c115 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -196,6 +196,12 @@
         inspector, parameters, Reference.methodFromMethod(method));
   }
 
+  static ApiModelingMethodVerificationHelper verifyThat(
+      CodeInspector inspector, TestParameters parameters, Constructor method) {
+    return new ApiModelingMethodVerificationHelper(
+        inspector, parameters, Reference.methodFromMethod(method));
+  }
+
   static ApiModelingFieldVerificationHelper verifyThat(
       CodeInspector inspector, TestParameters parameters, Field field) {
     return new ApiModelingFieldVerificationHelper(
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 28f7855..8546374 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
@@ -7,11 +7,9 @@
 import com.android.tools.r8.NeverInline;
 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.references.ClassReference;
+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.BooleanUtils;
@@ -50,41 +48,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepClassAndMembersRules(Main.class)
-        .addOptionsModification(
-            options -> {
-              DexItemFactory dexItemFactory = options.dexItemFactory();
-              StartupProfile startupProfile =
-                  StartupProfile.builder()
-                      .apply(
-                          builder ->
-                              getStartupClasses()
-                                  .forEach(
-                                      startupClass ->
-                                          builder.addStartupClass(
-                                              StartupClass.dexBuilder()
-                                                  .setClassReference(
-                                                      toDexType(startupClass, dexItemFactory))
-                                                  .build())))
-                      .build();
-              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(
             inspector ->
                 inspector
@@ -101,6 +64,24 @@
                                 .assertIsCompleteMergeGroup(
                                     OnClickHandlerA.class, OnClickHandlerB.class))
                     .assertNoOtherClassesMerged())
+        .addStartupProfileProviders(
+            new StartupProfileProvider() {
+
+              @Override
+              public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) {
+                for (Class<?> startupClass : getStartupClasses()) {
+                  ClassReference startupClassReference = Reference.classFromClass(startupClass);
+                  startupProfileBuilder.addStartupClass(
+                      startupClassBuilder ->
+                          startupClassBuilder.setClassReference(startupClassReference));
+                }
+              }
+
+              @Override
+              public Origin getOrigin() {
+                return Origin.unknown();
+              }
+            })
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
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 6d12c20..092d2ad 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.compilerapi.androidplatformbuild.AndroidPlatformBuildApiTest;
 import com.android.tools.r8.compilerapi.assertionconfiguration.AssertionConfigurationTest;
+import com.android.tools.r8.compilerapi.classconflictresolver.ClassConflictResolverTest;
 import com.android.tools.r8.compilerapi.desugardependencies.DesugarDependenciesTest;
 import com.android.tools.r8.compilerapi.diagnostics.UnsupportedFeaturesDiagnosticApiTest;
 import com.android.tools.r8.compilerapi.globalsynthetics.GlobalSyntheticsTest;
@@ -51,7 +52,8 @@
           UnsupportedFeaturesDiagnosticApiTest.ApiTest.class);
 
   private static final List<Class<? extends CompilerApiTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
-      ImmutableList.of(StartupProfileApiTest.ApiTest.class);
+      ImmutableList.of(
+          StartupProfileApiTest.ApiTest.class, ClassConflictResolverTest.ApiTest.class);
 
   private final TemporaryFolder temp;
 
diff --git a/src/test/java/com/android/tools/r8/compilerapi/androidplatformbuild/AndroidPlatformBuildApiTest.java b/src/test/java/com/android/tools/r8/compilerapi/androidplatformbuild/AndroidPlatformBuildApiTest.java
index 07d01cc..489c091 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/androidplatformbuild/AndroidPlatformBuildApiTest.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/androidplatformbuild/AndroidPlatformBuildApiTest.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.compilerapi.androidplatformbuild;
 
+import static com.android.tools.r8.MarkerMatcher.markerAndroidPlatformBuild;
 import static com.android.tools.r8.MarkerMatcher.markerMinApi;
 import static org.hamcrest.MatcherAssert.assertThat;
 
@@ -25,6 +26,8 @@
 
 public class AndroidPlatformBuildApiTest extends CompilerApiTestRunner {
 
+  public static final int MIN_API_LEVEL = 31;
+
   public AndroidPlatformBuildApiTest(TestParameters parameters) {
     super(parameters);
   }
@@ -51,7 +54,10 @@
     test.accept(new DexIndexedConsumer.ArchiveConsumer(output));
     assertThat(
         new CodeInspector(output).getMarkers(),
-        CoreMatchers.everyItem(markerMinApi(AndroidApiLevel.ANDROID_PLATFORM)));
+        CoreMatchers.everyItem(
+            CoreMatchers.allOf(
+                markerMinApi(AndroidApiLevel.getAndroidApiLevel(MIN_API_LEVEL)),
+                markerAndroidPlatformBuild())));
   }
 
   public static class ApiTest extends CompilerApiTest {
@@ -67,6 +73,7 @@
               .addLibraryFiles(getJava8RuntimeJar())
               .setProgramConsumer(programConsumer)
               .setAndroidPlatformBuild(true)
+              .setMinApiLevel(MIN_API_LEVEL)
               .build());
     }
 
@@ -78,6 +85,7 @@
               .addLibraryFiles(getJava8RuntimeJar())
               .setProgramConsumer(programConsumer)
               .setAndroidPlatformBuild(true)
+              .setMinApiLevel(MIN_API_LEVEL)
               .build());
     }
 
diff --git a/src/test/java/com/android/tools/r8/compilerapi/classconflictresolver/ClassConflictResolverTest.java b/src/test/java/com/android/tools/r8/compilerapi/classconflictresolver/ClassConflictResolverTest.java
new file mode 100644
index 0000000..d328392
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/classconflictresolver/ClassConflictResolverTest.java
@@ -0,0 +1,116 @@
+// 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.classconflictresolver;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ClassConflictResolver;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+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.origin.PathOrigin;
+import com.android.tools.r8.utils.BooleanBox;
+import com.google.common.collect.ImmutableSet;
+import java.nio.file.Paths;
+import org.junit.Test;
+
+public class ClassConflictResolverTest extends CompilerApiTestRunner {
+
+  public ClassConflictResolverTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<? extends CompilerApiTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    Origin originA = new PathOrigin(Paths.get("SourceA"));
+    Origin originB = new PathOrigin(Paths.get("SourceB"));
+    BooleanBox called = new BooleanBox(false);
+    new ApiTest(ApiTest.PARAMETERS)
+        .runD8(
+            originA,
+            originB,
+            (reference, origins, handler) -> {
+              called.set(true);
+              assertEquals(ImmutableSet.of(originA, originB), ImmutableSet.copyOf(origins));
+              return originA;
+            });
+    assertTrue(called.get());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Origin originA = new PathOrigin(Paths.get("SourceA"));
+    Origin originB = new PathOrigin(Paths.get("SourceB"));
+    BooleanBox called = new BooleanBox(false);
+    new ApiTest(ApiTest.PARAMETERS)
+        .runR8(
+            originA,
+            originB,
+            (reference, origins, handler) -> {
+              called.set(true);
+              assertEquals(ImmutableSet.of(originA, originB), ImmutableSet.copyOf(origins));
+              return originA;
+            });
+    assertTrue(called.get());
+  }
+
+  public static class ApiTest extends CompilerApiTest {
+
+    public ApiTest(Object parameters) {
+      super(parameters);
+    }
+
+    public void runD8(Origin originA, Origin originB, ClassConflictResolver resolver)
+        throws Exception {
+      D8.run(
+          D8Command.builder()
+              .addClassProgramData(getBytesForClass(getMockClass()), originA)
+              .addClassProgramData(getBytesForClass(getMockClass()), originB)
+              .addLibraryFiles(getJava8RuntimeJar())
+              .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+              .setClassConflictResolver(resolver)
+              .build());
+    }
+
+    public void runR8(Origin originA, Origin originB, ClassConflictResolver resolver)
+        throws Exception {
+      R8.run(
+          R8Command.builder()
+              .addClassProgramData(getBytesForClass(getMockClass()), originA)
+              .addClassProgramData(getBytesForClass(getMockClass()), originB)
+              .addLibraryFiles(getJava8RuntimeJar())
+              .setDisableTreeShaking(true)
+              .setDisableMinification(true)
+              .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+              .setClassConflictResolver(resolver)
+              .build());
+    }
+
+    @Test
+    public void testD8() throws Exception {
+      Origin originA = new PathOrigin(Paths.get("SourceA"));
+      Origin originB = new PathOrigin(Paths.get("SourceB"));
+      runD8(originA, originB, (reference, origins, handler) -> originA);
+    }
+
+    @Test
+    public void testR8() throws Exception {
+      Origin originA = new PathOrigin(Paths.get("SourceA"));
+      Origin originB = new PathOrigin(Paths.get("SourceB"));
+      runR8(originA, originB, (reference, origins, handler) -> originA);
+    }
+  }
+}
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
index f8bc404..808a767 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/startupprofile/StartupProfileApiTest.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/startupprofile/StartupProfileApiTest.java
@@ -5,6 +5,8 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.D8;
 import com.android.tools.r8.D8Command;
@@ -13,17 +15,30 @@
 import com.android.tools.r8.R8;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TextInputStream;
 import com.android.tools.r8.compilerapi.CompilerApiTest;
 import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.profile.art.ArtProfileClassRuleInfo;
+import com.android.tools.r8.profile.art.ArtProfileMethodRuleInfo;
+import com.android.tools.r8.profile.art.ArtProfileRulePredicate;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.startup.StartupProfileBuilder;
 import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 import java.util.function.BiConsumer;
 import org.junit.Test;
 
@@ -82,9 +97,8 @@
     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()),
+        new CodeInspector(output.resolve("classes2.dex")).clazz(test.getPostStartupMockClass()),
         isPresent());
   }
 
@@ -96,17 +110,50 @@
 
     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())));
+          // Create human-readable ART startup profile.
+          ClassReference mockClassReference = Reference.classFromClass(getMockClass());
+          ClosableByteArrayInputStream inputStream =
+              new ClosableByteArrayInputStream(mockClassReference.getDescriptor().getBytes());
+
+          // Create parser and parse ART profile.
+          List<ClassReference> seenClasses = new ArrayList<>();
+          startupProfileBuilder.addHumanReadableArtProfile(
+              new TextInputStream() {
+
+                @Override
+                public InputStream getInputStream() {
+                  return inputStream;
+                }
+
+                @Override
+                public Charset getCharset() {
+                  return StandardCharsets.UTF_8;
+                }
+              },
+              parserBuilder ->
+                  parserBuilder.setRulePredicate(
+                      new ArtProfileRulePredicate() {
+                        @Override
+                        public boolean testClassRule(
+                            ClassReference reference, ArtProfileClassRuleInfo classRuleInfo) {
+                          seenClasses.add(reference);
+                          return true;
+                        }
+
+                        @Override
+                        public boolean testMethodRule(
+                            MethodReference reference, ArtProfileMethodRuleInfo methodRuleInfo) {
+                          return true;
+                        }
+                      }));
+
+          // Verify rule predicate has been used and input stream is closed.
+          assertEquals(1, seenClasses.size());
+          assertEquals(mockClassReference, seenClasses.get(0));
+          assertTrue(inputStream.isClosed());
         }
 
         @Override
@@ -197,5 +244,24 @@
           Collections.singleton(startupProfileProvider);
       commandBuilder.addStartupProfileProviders(startupProfileProviders);
     }
+
+    private static class ClosableByteArrayInputStream extends ByteArrayInputStream {
+
+      private boolean closed;
+
+      public ClosableByteArrayInputStream(byte[] buf) {
+        super(buf);
+      }
+
+      @Override
+      public void close() throws IOException {
+        super.close();
+        closed = true;
+      }
+
+      public boolean isClosed() {
+        return closed;
+      }
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java b/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
index 0f0a782..7c3bb9d 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.util.List;
@@ -61,6 +62,12 @@
         .noTreeShaking()
         .setMode(CompilationMode.DEBUG)
         .setMinApi(parameters.getApiLevel())
+        .applyIf(
+            kotlinTestParameters.isKotlinDev(),
+            TestShrinkerBuilder::addDontWarnJavaLangReflectAnnotatedType)
+        .applyIf(
+            parameters.isCfRuntime() && kotlinTestParameters.isKotlinDev(),
+            TestShrinkerBuilder::addDontWarnJavaLangInvokeLambdaMetadataFactory)
         .compile()
         .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."));
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CollectionOfTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CollectionOfTest.java
new file mode 100644
index 0000000..c9f3f99
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CollectionOfTest.java
@@ -0,0 +1,92 @@
+// 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;
+
+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.JDK8;
+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.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.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 CollectionOfTest 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 + "collectionof.jar");
+  private static final String EXPECTED_OUTPUT_BACKPORT = StringUtils.lines("false", "false");
+  private static final String EXPECTED_OUTPUT_CORRECT = StringUtils.lines("npe", "npe");
+  private static final String MAIN_CLASS = "collectionof.CollectionOfMain";
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        getJdk8Jdk11(),
+        DEFAULT_SPECIFICATIONS);
+  }
+
+  public CollectionOfTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
+    this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
+  }
+
+  private String getExpectedOutput(boolean desugaredLib) {
+    if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.R)) {
+      return EXPECTED_OUTPUT_CORRECT;
+    }
+    if (desugaredLib && libraryDesugaringSpecification != JDK8) {
+      if (parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) {
+        return EXPECTED_OUTPUT_CORRECT;
+      }
+      // TODO(b/243679691): This should also be correct, but is not because we use backports in
+      //  partial desugaring.
+      return EXPECTED_OUTPUT_BACKPORT;
+    }
+    return EXPECTED_OUTPUT_BACKPORT;
+  }
+
+  @Test
+  public void testCollectionOf() throws Throwable {
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addProgramFiles(INPUT_JAR)
+        .addKeepMainRule(MAIN_CLASS)
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(getExpectedOutput(true));
+  }
+
+  @Test
+  public void testCollectionOfReference() throws Throwable {
+    Assume.assumeTrue(
+        "Run only once",
+        libraryDesugaringSpecification == JDK8 && compilationSpecification == D8_L8DEBUG);
+    testForD8()
+        .addProgramFiles(INPUT_JAR)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(getExpectedOutput(false));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
index 8c572cf..be94723 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
@@ -99,6 +99,12 @@
         .allowDiagnosticMessages()
         .allowUnusedDontWarnKotlinReflectJvmInternal(
             kotlinParameters.getCompiler().isNot(KOTLINC_1_3_72))
+        .applyIfR8TestBuilder(
+            b -> {
+              if (kotlinParameters.isKotlinDev()) {
+                b.addDontWarnJavaLangReflectAnnotatedType();
+              }
+            })
         .compile()
         .inspect(
             i -> {
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 380f2d5..4b41588 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
@@ -218,6 +218,11 @@
     return this;
   }
 
+  public DesugaredLibraryTestBuilder<T> applyIfR8TestBuilder(Consumer<R8TestBuilder<?>> consumer) {
+    withR8TestBuilder(consumer);
+    return this;
+  }
+
   public DesugaredLibraryTestBuilder<T> allowDiagnosticWarningMessages() {
     withR8TestBuilder(R8TestBuilder::allowDiagnosticWarningMessages);
     return this;
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 a44f221..dbeb66b 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1719Test.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1719Test.java
@@ -144,17 +144,20 @@
     R8TestCompileResult r8CompileResult =
         compileApplicationWithR8(
             testBuilder ->
-                testBuilder.addOptionsModification(
-                    options -> {
-                      if (startupProfileProvider != null) {
-                        options
-                            .getStartupOptions()
-                            .setStartupProfileProvider(startupProfileProvider)
-                            .setEnableMinimalStartupDex(enableMinimalStartupDex)
-                            .setEnableStartupBoundaryOptimizations(
-                                enableStartupBoundaryOptimizations);
-                      }
-                    }));
+                testBuilder
+                    .addOptionsModification(
+                        options -> {
+                          if (startupProfileProvider != null) {
+                            options
+                                .getStartupOptions()
+                                .setEnableMinimalStartupDex(enableMinimalStartupDex)
+                                .setEnableStartupBoundaryOptimizations(
+                                    enableStartupBoundaryOptimizations);
+                          }
+                        })
+                    .applyIf(
+                        startupProfileProvider != null,
+                        b -> b.addStartupProfileProviders(startupProfileProvider)));
 
     // Compile desugared library using cf backend (without keep rules).
     L8TestCompileResult l8CompileResult = compileDesugaredLibraryWithL8();
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 c7211db..2527b81 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,16 +13,12 @@
 import com.android.tools.r8.ArchiveProgramResourceProvider;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.R8FullTestBuilder;
-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.experimental.startup.StartupProfileProviderUtils;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.ZipUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -238,34 +234,19 @@
       boolean enableStartupBoundaryOptimizations,
       Path outDirectory)
       throws Exception {
-    StartupProfileProvider startupProfileProvider =
-        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(
-                options ->
-                    options
-                        .getStartupOptions()
-                        .setEnableMinimalStartupDex(enableMinimalStartupDex)
-                        .setEnableStartupBoundaryOptimizations(enableStartupBoundaryOptimizations)
-                        .setStartupProfileProvider(startupProfileProvider)),
+            testBuilder
+                .addOptionsModification(
+                    options ->
+                        options
+                            .getStartupOptions()
+                            .setEnableMinimalStartupDex(enableMinimalStartupDex)
+                            .setEnableStartupBoundaryOptimizations(
+                                enableStartupBoundaryOptimizations))
+                .addStartupProfileProviders(
+                    StartupProfileProviderUtils.createFromHumanReadableArtProfile(
+                        chromeDirectory.resolve("startup.txt"))),
         outDirectory);
   }
 
diff --git a/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java b/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java
index 90771b4..a52c762 100644
--- a/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java
+++ b/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java
@@ -4,25 +4,39 @@
 
 package com.android.tools.r8.invalid;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.jasmin.JasminTestBase;
-import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
-import java.nio.charset.Charset;
 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 DuplicateDefinitionsTest extends JasminTestBase {
 
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultDexRuntime().withApiLevel(AndroidApiLevel.B).build();
+  }
+
+  private final TestParameters parameters;
+
+  public DuplicateDefinitionsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
   @Test
   public void testDuplicateMethods() throws Exception {
     JasminBuilder jasminBuilder = new JasminBuilder();
@@ -32,38 +46,37 @@
     classBuilder.addVirtualMethod("method", "V", ".limit locals 1", ".limit stack 0", "return");
     classBuilder.addVirtualMethod("method", "V", ".limit locals 1", ".limit stack 0", "return");
 
-    // Run D8 and intercept warnings.
-    PrintStream stderr = System.err;
-    ByteArrayOutputStream baos = new ByteArrayOutputStream();
-    System.setErr(new PrintStream(baos));
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(jasminBuilder.buildClasses())
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics
+                    .assertOnlyWarnings()
+                    .assertWarningsMatch(
+                        diagnosticMessage(
+                            containsString(
+                                "Ignoring an implementation of the method `void"
+                                    + " C.main(java.lang.String[])` because it has multiple"
+                                    + " definitions")),
+                        diagnosticMessage(
+                            containsString(
+                                "Ignoring an implementation of the method `void C.method()` because"
+                                    + " it has multiple definitions"))))
+        .inspect(
+            inspector -> {
+              ClassSubject clazz = inspector.clazz("C");
+              assertThat(clazz, isPresent());
 
-    AndroidApp app = compileWithD8(jasminBuilder.build());
+              // There are two direct methods, but only because one is <init>.
+              assertEquals(
+                  2, clazz.getDexProgramClass().getMethodCollection().numberOfDirectMethods());
+              assertThat(clazz.method("void", "<init>", ImmutableList.of()), isPresent());
 
-    String output = new String(baos.toByteArray(), Charset.defaultCharset());
-    System.setOut(stderr);
-
-    // Check that warnings were emitted.
-    assertThat(
-        output,
-        containsString(
-            "Ignoring an implementation of the method `void C.main(java.lang.String[])` because "
-                + "it has multiple definitions"));
-    assertThat(
-        output,
-        containsString(
-            "Ignoring an implementation of the method `void C.method()` because "
-                + "it has multiple definitions"));
-
-    CodeInspector inspector = new CodeInspector(app);
-    ClassSubject clazz = inspector.clazz("C");
-    assertThat(clazz, isPresent());
-
-    // There are two direct methods, but only because one is <init>.
-    assertEquals(2, clazz.getDexProgramClass().getMethodCollection().numberOfDirectMethods());
-    assertThat(clazz.method("void", "<init>", ImmutableList.of()), isPresent());
-
-    // There is only one virtual method.
-    assertEquals(1, clazz.getDexProgramClass().getMethodCollection().numberOfVirtualMethods());
+              // There is only one virtual method.
+              assertEquals(
+                  1, clazz.getDexProgramClass().getMethodCollection().numberOfVirtualMethods());
+            });
   }
 
   @Test
@@ -75,26 +88,26 @@
     classBuilder.addStaticField("staticFld", "LC;", null);
     classBuilder.addStaticField("staticFld", "LC;", null);
 
-    // Run D8 and intercept warnings.
-    PrintStream stderr = System.err;
-    ByteArrayOutputStream baos = new ByteArrayOutputStream();
-    System.setErr(new PrintStream(baos));
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(jasminBuilder.buildClasses())
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics
+                    .assertOnlyWarnings()
+                    .assertWarningsMatch(
+                        diagnosticMessage(
+                            containsString("Field `C C.fld` has multiple definitions")),
+                        diagnosticMessage(
+                            containsString("Field `C C.staticFld` has multiple definitions"))))
+        .inspect(
+            inspector -> {
+              ClassSubject clazz = inspector.clazz("C");
+              assertThat(clazz, isPresent());
 
-    AndroidApp app = compileWithD8(jasminBuilder.build());
-
-    String output = new String(baos.toByteArray(), Charset.defaultCharset());
-    System.setOut(stderr);
-
-    // Check that warnings were emitted.
-    assertThat(output, containsString("Field `C C.fld` has multiple definitions"));
-    assertThat(output, containsString("Field `C C.staticFld` has multiple definitions"));
-
-    CodeInspector inspector = new CodeInspector(app);
-    ClassSubject clazz = inspector.clazz("C");
-    assertThat(clazz, isPresent());
-
-    // Redundant fields have been removed.
-    assertEquals(1, clazz.getDexProgramClass().instanceFields().size());
-    assertEquals(1, clazz.getDexProgramClass().staticFields().size());
+              // Redundant fields have been removed.
+              assertEquals(1, clazz.getDexProgramClass().instanceFields().size());
+              assertEquals(1, clazz.getDexProgramClass().staticFields().size());
+            });
   }
 }
diff --git a/src/test/java/com/android/tools/r8/invalid/DuplicateProgramTypesTest.java b/src/test/java/com/android/tools/r8/invalid/DuplicateProgramTypesTest.java
index e8c8d39..1efe38f 100644
--- a/src/test/java/com/android/tools/r8/invalid/DuplicateProgramTypesTest.java
+++ b/src/test/java/com/android/tools/r8/invalid/DuplicateProgramTypesTest.java
@@ -10,10 +10,12 @@
 import static org.hamcrest.CoreMatchers.hasItems;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThrows;
 
+import com.android.tools.r8.BaseCompilerCommand;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
@@ -21,6 +23,8 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.SetUtils;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -32,7 +36,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withNoneRuntime().build();
+    return getTestParameters().withDefaultDexRuntime().withApiLevel(AndroidApiLevel.B).build();
   }
 
   public DuplicateProgramTypesTest(TestParameters parameters) {
@@ -55,39 +59,113 @@
         }
       };
 
+  private void addDuplicateDefinitions(BaseCompilerCommand.Builder<?, ?> builder) throws Exception {
+    byte[] bytes = ToolHelper.getClassAsBytes(TestClass.class);
+    builder.addClassProgramData(bytes, originA);
+    builder.addClassProgramData(bytes, originB);
+  }
+
+  private void addResolvingHandler(BaseCompilerCommand.Builder<?, ?> builder) throws Exception {
+    builder.setClassConflictResolver(
+        (reference, origins, handler) -> {
+          assertEquals(
+              SetUtils.newIdentityHashSet(originA, originB), SetUtils.newIdentityHashSet(origins));
+          return originA;
+        });
+  }
+
+  private void addNonResolvingHandler(BaseCompilerCommand.Builder<?, ?> builder) throws Exception {
+    builder.setClassConflictResolver(
+        (reference, origins, handler) -> {
+          assertEquals(
+              SetUtils.newIdentityHashSet(originA, originB), SetUtils.newIdentityHashSet(origins));
+          return null;
+        });
+  }
+
+  private void checkErrorDiagnostic(TestDiagnosticMessages diagnostics) {
+    diagnostics.assertOnlyErrors();
+    diagnostics.assertErrorsCount(1);
+    DuplicateTypesDiagnostic diagnostic = (DuplicateTypesDiagnostic) diagnostics.getErrors().get(0);
+    assertEquals(Position.UNKNOWN, diagnostic.getPosition());
+    assertThat(diagnostic.getType(), equalTo(Reference.classFromClass(TestClass.class)));
+    assertThat(diagnostic.getOrigin(), anyOf(equalTo(originA), equalTo(originB)));
+    assertThat(diagnostic.getOrigins(), hasItems(originA, originB));
+    assertThat(
+        diagnostic.getDiagnosticMessage(),
+        allOf(
+            containsString("defined multiple"),
+            containsString("SourceA"),
+            containsString("SourceB")));
+  }
+
   @Test
-  public void test() throws Exception {
-    try {
-      byte[] bytes = ToolHelper.getClassAsBytes(TestClass.class);
-      testForD8()
-          .setMinApi(parameters.getRuntime())
-          .apply(
-              b -> {
-                b.getBuilder().addClassProgramData(bytes, originA);
-                b.getBuilder().addClassProgramData(bytes, originB);
-              })
-          .compileWithExpectedDiagnostics(
-              diagnostics -> {
-                diagnostics.assertOnlyErrors();
-                diagnostics.assertErrorsCount(1);
-                DuplicateTypesDiagnostic diagnostic =
-                    (DuplicateTypesDiagnostic) diagnostics.getErrors().get(0);
-                assertEquals(Position.UNKNOWN, diagnostic.getPosition());
-                assertThat(
-                    diagnostic.getType(), equalTo(Reference.classFromClass(TestClass.class)));
-                assertThat(diagnostic.getOrigin(), anyOf(equalTo(originA), equalTo(originB)));
-                assertThat(diagnostic.getOrigins(), hasItems(originA, originB));
-                assertThat(
-                    diagnostic.getDiagnosticMessage(),
-                    allOf(
-                        containsString("defined multiple"),
-                        containsString("SourceA"),
-                        containsString("SourceB")));
-              });
-    } catch (CompilationFailedException e) {
-      return; // Success.
-    }
-    fail("Expected test to fail with CompilationFailedException");
+  public void testDefaultError() throws Exception {
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForD8(parameters.getBackend())
+                .setMinApi(parameters.getApiLevel())
+                .apply(b -> addDuplicateDefinitions(b.getBuilder()))
+                .compileWithExpectedDiagnostics(this::checkErrorDiagnostic));
+  }
+
+  @Test
+  public void testResolvedConflictD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .apply(
+            b -> {
+              addDuplicateDefinitions(b.getBuilder());
+              addResolvingHandler(b.getBuilder());
+            })
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world");
+  }
+
+  @Test
+  public void testResolvedConflictR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClass.class)
+        .apply(
+            b -> {
+              addDuplicateDefinitions(b.getBuilder());
+              addResolvingHandler(b.getBuilder());
+            })
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world");
+  }
+
+  @Test
+  public void testNonResolvedConflictD8() throws Exception {
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForD8(parameters.getBackend())
+                .setMinApi(parameters.getApiLevel())
+                .apply(
+                    b -> {
+                      addDuplicateDefinitions(b.getBuilder());
+                      addNonResolvingHandler(b.getBuilder());
+                    })
+                .compileWithExpectedDiagnostics(this::checkErrorDiagnostic));
+  }
+
+  @Test
+  public void testNonResolvedConflictR8() throws Exception {
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForR8(parameters.getBackend())
+                .setMinApi(parameters.getApiLevel())
+                .addKeepMainRule(TestClass.class)
+                .apply(
+                    b -> {
+                      addDuplicateDefinitions(b.getBuilder());
+                      addNonResolvingHandler(b.getBuilder());
+                    })
+                .compileWithExpectedDiagnostics(this::checkErrorDiagnostic));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java
index e14d7a6..acd52f2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -34,10 +35,17 @@
   }
 
   private void test(Collection<String> rules) throws Exception {
+    boolean notShrinking = rules.contains("-dontshrink");
     testForR8(parameters.getBackend())
         .addProgramFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST))
         .addKeepRules(rules)
+        .applyIf(
+            notShrinking && kotlinParameters.isKotlinDev(),
+            TestShrinkerBuilder::addDontWarnJavaLangReflectAnnotatedType)
+        .applyIf(
+            notShrinking && kotlinParameters.isKotlinDev() && parameters.isCfRuntime(),
+            TestShrinkerBuilder::addDontWarnJavaLangInvokeLambdaMetadataFactory)
         .addKeepAttributes(ProguardKeepAttributes.SIGNATURE)
         .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
         .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeep.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeepTest.java
similarity index 90%
rename from src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeep.java
rename to src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeepTest.java
index ee16233..a862b2c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeep.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeepTest.java
@@ -22,7 +22,7 @@
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class MetadataRewriteDependentKeep extends KotlinMetadataTestBase {
+public class MetadataRewriteDependentKeepTest extends KotlinMetadataTestBase {
 
   @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
@@ -33,7 +33,7 @@
 
   private final TestParameters parameters;
 
-  public MetadataRewriteDependentKeep(
+  public MetadataRewriteDependentKeepTest(
       TestParameters parameters, KotlinTestParameters kotlinParameters) {
     super(kotlinParameters);
     this.parameters = parameters;
@@ -56,7 +56,8 @@
     // All kept classes should have their kotlin metadata.
     for (FoundClassSubject clazz : inspector.allClasses()) {
       if (clazz.getFinalName().startsWith("kotlin.io")
-          || clazz.getFinalName().equals("kotlin.Metadata")) {
+          || clazz.getFinalName().equals("kotlin.Metadata")
+          || clazz.getFinalName().equals("kotlin.jvm.JvmName")) {
         assertNotNull(clazz.getKotlinClassMetadata());
       } else {
         assertNull(clazz.getKotlinClassMetadata());
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
index 619f4a1..99cdd63 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
@@ -15,10 +15,13 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
-import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
@@ -28,8 +31,13 @@
 import com.android.tools.r8.utils.codeinspector.KmTypeSubject;
 import com.android.tools.r8.utils.codeinspector.KmValueParameterSubject;
 import com.android.tools.r8.utils.codeinspector.Matchers;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -184,8 +192,7 @@
   @Test
   public void testMetadataInExtensionFunction_renamedKotlinSources() throws Exception {
     assumeTrue(kotlinc.getCompilerVersion().isGreaterThanOrEqualTo(KOTLINC_1_4_20));
-    Box<String> renamedKtHolder = new Box<>();
-    Path libJar =
+    R8TestCompileResult r8LibraryResult =
         testForR8(parameters.getBackend())
             .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
             .addProgramFiles(extLibJarMap.getForConfiguration(kotlinc, targetVersion))
@@ -201,21 +208,65 @@
             .addKeepAttributes(ProguardKeepAttributes.SIGNATURE)
             .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
             .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
-            .compile()
-            .inspect(
-                inspector -> {
-                  ClassSubject clazz = inspector.clazz(PKG + ".extension_function_lib.BKt");
-                  assertThat(clazz, isPresentAndRenamed());
-                  renamedKtHolder.set(clazz.getFinalName());
-                })
-            .writeToZip();
+            .compile();
+    Path kotlinSourcePath = getKotlinFileInTest(PKG_PREFIX + "/extension_function_app", "main");
 
-    kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
-        .addClasspathFiles(libJar)
-        .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/extension_function_app", "main"))
-        .setOutputPath(temp.newFolder().toPath())
-        // TODO(b/242289529): Expect that we can compile without errors.
-        .compile(true);
+    String kotlinSource = FileUtils.readTextFile(kotlinSourcePath, StandardCharsets.UTF_8);
+
+    CodeInspector inspector = r8LibraryResult.inspector();
+
+    ClassSubject clazz = inspector.clazz(PKG + ".extension_function_lib.BKt");
+    assertThat(clazz, isPresentAndRenamed());
+
+    // Rewrite the source kotlin files that reference the four extension methods into their renamed
+    // name by changing the import statement and the actual call.
+    String[] methodNames = new String[] {"extension", "csHash", "longArrayHash", "myApply"};
+    for (String methodName : methodNames) {
+      MethodSubject method = clazz.uniqueMethodWithName(methodName);
+      assertThat(method, isPresentAndRenamed());
+      String finalMethodName = method.getFinalName();
+      kotlinSource =
+          kotlinSource.replace(
+              "import com.android.tools.r8.kotlin.metadata.extension_function_lib." + methodName,
+              "import "
+                  + DescriptorUtils.getPackageNameFromTypeName(clazz.getFinalName())
+                  + "."
+                  + finalMethodName);
+      kotlinSource = kotlinSource.replace(")." + methodName, ")." + finalMethodName);
+    }
+
+    Path newSource = temp.newFolder().toPath().resolve("main.kt");
+    Files.write(newSource, kotlinSource.getBytes(StandardCharsets.UTF_8));
+
+    Path libJar = r8LibraryResult.writeToZip();
+    Path tempUnzipPath = temp.newFolder().toPath();
+    List<String> kotlinModuleFiles = new ArrayList<>();
+    ZipUtils.unzip(
+        libJar,
+        tempUnzipPath,
+        f -> {
+          if (f.getName().endsWith(".kotlin_module")) {
+            kotlinModuleFiles.add(f.getName());
+          }
+          return false;
+        });
+    assertEquals(Collections.singletonList("META-INF/main.kotlin_module"), kotlinModuleFiles);
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(newSource)
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    if (kotlinParameters.isOlderThan(KOTLINC_1_4_20)) {
+      return;
+    }
+
+    testForJvm()
+        .addRunClasspathFiles(kotlinc.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".extension_function_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspectRenamed(CodeInspector inspector) {
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 5a3960e..11c9e7b 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
@@ -15,6 +15,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -36,7 +37,7 @@
 
 @RunWith(Parameterized.class)
 public class MetadataRewriteInMultifileClassTest extends KotlinMetadataTestBase {
-  private static final String EXPECTED = StringUtils.lines(", 1, 2, 3");
+  private static final String EXPECTED = StringUtils.lines(", 1, 2, 3", ", 1, 2, 3");
 
   private final TestParameters parameters;
 
@@ -44,7 +45,11 @@
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
+        getKotlinTestParameters()
+            .withAllCompilers()
+            .withOldCompilersStartingFrom(KotlinCompilerVersion.KOTLINC_1_4_20)
+            .withAllTargetVersions()
+            .build());
   }
 
   public MetadataRewriteInMultifileClassTest(
@@ -95,9 +100,7 @@
             .addClasspathFiles(libJar)
             .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/multifileclass_app", "main"))
             .setOutputPath(temp.newFolder().toPath())
-            // TODO(b/151193860): update to just .compile() once fixed.
             .compileRaw();
-    // TODO(b/151193860): should be able to compile!
     assertNotEquals(0, kotlinTestCompileResult.exitCode);
     assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: join"));
   }
@@ -113,8 +116,6 @@
     assertThat(joinOfInt, not(isPresent()));
 
     inspectMetadataForFacade(inspector, util);
-    // TODO(b/156290332): Seems like this test is incorrect and should never work.
-    // inspectSignedKt(inspector);
   }
 
   @Test
@@ -126,6 +127,7 @@
             // Keep UtilKt#comma*Join*().
             .addKeepRules("-keep class **.UtilKt")
             .addKeepRules("-keep,allowobfuscation class **.UtilKt__SignedKt")
+            .addKeepRules("-keep,allowobfuscation class **.UtilKt__UnsignedKt")
             .addKeepRules("-keepclassmembers class * { ** comma*Join*(...); }")
             // Keep yet rename joinOf*(String).
             .addKeepRules("-keepclassmembers,allowobfuscation class * { ** joinOf*(...); }")
@@ -134,16 +136,18 @@
             .inspect(this::inspectRenamed)
             .writeToZip();
 
-    ProcessResult kotlinTestCompileResult =
+    Path output =
         kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
             .addClasspathFiles(libJar)
             .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/multifileclass_app", "main"))
             .setOutputPath(temp.newFolder().toPath())
-            // TODO(b/151193860): update to just .compile() once fixed.
-            .compileRaw();
-    // TODO(b/151193860): should be able to compile!
-    assertNotEquals(0, kotlinTestCompileResult.exitCode);
-    assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: join"));
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(kotlinc.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".multifileclass_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspectRenamed(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java
index 16fc35d..24b3e58 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java
@@ -66,7 +66,8 @@
     // All kept classes should have their kotlin metadata.
     for (FoundClassSubject clazz : inspector.allClasses()) {
       if (clazz.getFinalName().startsWith("kotlin.io")
-          || clazz.getFinalName().equals("kotlin.Metadata")) {
+          || clazz.getFinalName().equals("kotlin.Metadata")
+          || clazz.getFinalName().equals("kotlin.jvm.JvmName")) {
         assertNotNull(clazz.getKotlinClassMetadata());
         assertNotNull(clazz.getKotlinClassMetadata().getHeader().getData2());
       } else {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_app/main.kt
index 72e86b4..dc7d16e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_app/main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_function_app/main.kt
@@ -13,7 +13,7 @@
   B().doStuff()
   B().extension()
 
-  "R8".csHash()
+  ("R8").csHash()
   longArrayOf(42L).longArrayHash()
   B().myApply { this.doStuff() }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/multifileclass_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/multifileclass_app/main.kt
index ce098bf..6d81d8c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/multifileclass_app/main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/multifileclass_app/main.kt
@@ -5,7 +5,18 @@
 
 import com.android.tools.r8.kotlin.metadata.multifileclass_lib.join
 
-fun main() {
+fun signed() {
   val s = sequenceOf(1, 2, 3)
   println(s.join())
 }
+
+@OptIn(ExperimentalUnsignedTypes::class)
+fun unsigned() {
+  val s = sequenceOf(1u, 2u, 3u)
+  println(s.join())
+}
+
+fun main() {
+  signed()
+  unsigned()
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
index 1cc87e4..a5400d6 100644
--- a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompileResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.kotlin.metadata.KotlinMetadataTestBase;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
@@ -98,6 +99,12 @@
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
         .allowDiagnosticMessages()
         .allowUnusedDontWarnKotlinReflectJvmInternal(kotlinc.isNot(KOTLINC_1_3_72))
+        .applyIf(
+            parameters.isCfRuntime() && kotlinParameters.isKotlinDev(),
+            TestShrinkerBuilder::addDontWarnJavaLangInvokeLambdaMetadataFactory)
+        .applyIf(
+            kotlinParameters.isKotlinDev(),
+            TestShrinkerBuilder::addDontWarnJavaLangReflectAnnotatedType)
         .compile()
         .assertNoErrorMessages()
         // -keepattributes Signature is added in kotlin-reflect from version 1.4.20.
diff --git a/src/test/java/com/android/tools/r8/profile/art/diagnostic/HumanReadableArtProfileParserErrorDiagnosticTest.java b/src/test/java/com/android/tools/r8/profile/art/diagnostic/HumanReadableArtProfileParserErrorDiagnosticTest.java
new file mode 100644
index 0000000..aee021b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/diagnostic/HumanReadableArtProfileParserErrorDiagnosticTest.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.profile.art.diagnostic;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.equalTo;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.startup.StartupProfileBuilder;
+import com.android.tools.r8.startup.StartupProfileProvider;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ConsumerUtils;
+import com.android.tools.r8.utils.UTF8TextInputStream;
+import java.io.ByteArrayInputStream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class HumanReadableArtProfileParserErrorDiagnosticTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void test() throws Exception {
+    testForD8()
+        .addProgramClasses(Main.class)
+        .addStartupProfileProviders(
+            new StartupProfileProvider() {
+              @Override
+              public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) {
+
+                startupProfileBuilder.addHumanReadableArtProfile(
+                    new UTF8TextInputStream(
+                        new ByteArrayInputStream("INVALID1\nINVALID2".getBytes())),
+                    ConsumerUtils.emptyConsumer());
+              }
+
+              @Override
+              public Origin getOrigin() {
+                return Origin.unknown();
+              }
+            })
+        .release()
+        .setMinApi(AndroidApiLevel.LATEST)
+        .compileWithExpectedDiagnostics(this::inspectDiagnostics);
+  }
+
+  private void inspectDiagnostics(TestDiagnosticMessages diagnostics) {
+    diagnostics.assertErrorsMatch(
+        allOf(
+            diagnosticType(HumanReadableArtProfileParserErrorDiagnostic.class),
+            diagnosticMessage(
+                equalTo("Unable to parse rule at line 1 from ART profile: INVALID1"))),
+        allOf(
+            diagnosticType(HumanReadableArtProfileParserErrorDiagnostic.class),
+            diagnosticMessage(
+                equalTo("Unable to parse rule at line 2 from ART profile: INVALID2"))));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 9c3bbea..2b8d68b 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -911,7 +911,8 @@
     verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     assertEquals(
-        "-keepattributes RuntimeVisibleAnnotations", config.getKeepAttributes().toString());
+        "-keepattributes RuntimeVisibleAnnotations,RuntimeInvisibleAnnotations",
+        config.getKeepAttributes().toString());
     assertEquals(
         StringUtils.joinLines("-keep class kotlin.Metadata {", "  *;", "}"),
         config.getRules().get(0).toString());
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
index 3349571..8105b81 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfRuleWithFieldAnnotation.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfRuleWithFieldAnnotation.java
@@ -3,9 +3,12 @@
 // 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.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.ProguardVersion;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -19,6 +22,15 @@
 public class IfRuleWithFieldAnnotation extends TestBase {
 
   static final String EXPECTED = "foobar";
+  public static final String CONDITIONAL_KEEP_RULE =
+      "-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"
+          + "}";
 
   private final TestParameters parameters;
 
@@ -36,15 +48,7 @@
     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"
-                + "}")
+        .addKeepRules(CONDITIONAL_KEEP_RULE)
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(
@@ -54,13 +58,75 @@
             })
         .run(parameters.getRuntime(), Foo.class)
         .assertSuccessWithOutputLines(EXPECTED);
+    // We should remove the class if the usage of the field is not live.
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Foo.class, Bar.class, SerializedName.class, FooNotCallingBar.class)
+        .addKeepMainRule(FooNotCallingBar.class)
+        .addKeepRules(CONDITIONAL_KEEP_RULE)
+        .allowUnusedProguardConfigurationRules()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(Bar.class), isAbsent());
+            })
+        .run(parameters.getRuntime(), FooNotCallingBar.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testProguard() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForProguard(ProguardVersion.V7_0_0)
+        .addProgramClasses(Foo.class, Bar.class, SerializedName.class)
+        .addDontWarn(getClass())
+        .addKeepMainRule(Foo.class)
+        .addKeepRules(CONDITIONAL_KEEP_RULE)
+        .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);
+    testForProguard(ProguardVersion.V7_0_0)
+        .addProgramClasses(Foo.class, Bar.class, SerializedName.class, FooNotCallingBar.class)
+        .addDontWarn(getClass())
+        .addKeepMainRule(FooNotCallingBar.class)
+        .noMinification()
+        .addKeepRules(CONDITIONAL_KEEP_RULE)
+        .compile()
+        .inspect(
+            codeInspector -> {
+              // The if rule above will make proguard keep the class and the constructor, but not
+              // the field. If we don't have the rule, proguard will remove the class, see test
+              // below.
+              assertThat(codeInspector.clazz(Bar.class), isPresent());
+              assertThat(codeInspector.clazz(Bar.class).init("int"), isPresent());
+              assertThat(codeInspector.clazz(Bar.class).field("int", "value"), isAbsent());
+            })
+        .run(parameters.getRuntime(), FooNotCallingBar.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+    // Test that without the conditional keep rule proguard correctly removes the class.
+    testForProguard(ProguardVersion.V7_0_0)
+        .addProgramClasses(Foo.class, Bar.class, SerializedName.class, FooNotCallingBar.class)
+        .addDontWarn(getClass())
+        .addKeepMainRule(FooNotCallingBar.class)
+        .noMinification()
+        .compile()
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(Bar.class), isAbsent());
+            })
+        .run(parameters.getRuntime(), FooNotCallingBar.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);
@@ -84,6 +150,12 @@
     }
   }
 
+  public static class FooNotCallingBar {
+    public static void main(String[] args) {
+      System.out.println("foobar");
+    }
+  }
+
   public static class Bar {
     @SerializedName public int 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
index 6eff67b..23e8577 100644
--- a/src/test/java/com/android/tools/r8/startup/InliningOutOfStartupPartitionTest.java
+++ b/src/test/java/com/android/tools/r8/startup/InliningOutOfStartupPartitionTest.java
@@ -11,12 +11,10 @@
 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.profile.ExternalStartupClass;
+import com.android.tools.r8.startup.profile.ExternalStartupItem;
+import com.android.tools.r8.startup.profile.ExternalStartupMethod;
 import com.android.tools.r8.startup.utils.StartupTestingUtils;
 import com.android.tools.r8.utils.MethodReferenceUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -41,12 +39,12 @@
 
   @Test
   public void test() throws Exception {
-    List<StartupItem<ClassReference, MethodReference, ?>> startupItems =
+    List<ExternalStartupItem> startupItems =
         ImmutableList.of(
-            StartupClass.referenceBuilder()
+            ExternalStartupClass.builder()
                 .setClassReference(Reference.classFromClass(Main.class))
                 .build(),
-            StartupMethod.referenceBuilder()
+            ExternalStartupMethod.builder()
                 .setMethodReference(MethodReferenceUtils.mainMethod(Main.class))
                 .build());
     testForR8(parameters.getBackend())
@@ -60,12 +58,14 @@
             inspector -> {
               ClassSubject mainClassSubject = inspector.clazz(Main.class);
               assertThat(mainClassSubject, isPresent());
+              // The postStartupMethod() should be inlined into PostStartupClass.runPostStartup().
               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());
+              assertThat(postStartupClassSubject, isPresent());
+              // The runPostStartup() method must not be inlined into Main.main().
+              assertThat(
+                  postStartupClassSubject.uniqueMethodWithName("runPostStartup"), isPresent());
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("Hello, world!");
diff --git a/src/test/java/com/android/tools/r8/startup/InstrumentationServerImpl.java b/src/test/java/com/android/tools/r8/startup/InstrumentationServerImpl.java
index 328b485..1f19f3c 100644
--- a/src/test/java/com/android/tools/r8/startup/InstrumentationServerImpl.java
+++ b/src/test/java/com/android/tools/r8/startup/InstrumentationServerImpl.java
@@ -25,14 +25,10 @@
     return InstrumentationServerImpl.INSTANCE;
   }
 
-  public static void addNonSyntheticMethod(String descriptor) {
+  public static void addMethod(String descriptor) {
     getInstance().addLine(descriptor);
   }
 
-  public static void addSyntheticMethod(String descriptor) {
-    getInstance().addLine('S' + descriptor);
-  }
-
   private void addLine(String line) {
     synchronized (lines) {
       if (!lines.add(line)) {
diff --git a/src/test/java/com/android/tools/r8/startup/MinimalStartupDexTest.java b/src/test/java/com/android/tools/r8/startup/MinimalStartupDexTest.java
index 35df0b6..db55acb 100644
--- a/src/test/java/com/android/tools/r8/startup/MinimalStartupDexTest.java
+++ b/src/test/java/com/android/tools/r8/startup/MinimalStartupDexTest.java
@@ -12,17 +12,17 @@
 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.StartupItem;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.profile.art.ArtProfileBuilderUtils.SyntheticToSyntheticContextGeneralization;
+import com.android.tools.r8.startup.profile.ExternalStartupItem;
 import com.android.tools.r8.startup.utils.StartupTestingUtils;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Set;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -45,7 +45,7 @@
 
   @Test
   public void test() throws Exception {
-    List<StartupItem<ClassReference, MethodReference, ?>> startupList = new ArrayList<>();
+    Set<ExternalStartupItem> startupList = new LinkedHashSet<>();
     testForD8(parameters.getBackend())
         .addInnerClasses(getClass())
         .apply(
@@ -55,7 +55,9 @@
         .compile()
         .addRunClasspathFiles(StartupTestingUtils.getAndroidUtilLog(temp))
         .run(parameters.getRuntime(), Main.class)
-        .apply(StartupTestingUtils.removeStartupListFromStdout(startupList::add))
+        .apply(
+            StartupTestingUtils.removeStartupListFromStdout(
+                startupList::add, SyntheticToSyntheticContextGeneralization.createForR8()))
         .assertSuccessWithOutputLines(getExpectedOutput());
 
     testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java b/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java
index bc0292f..3709e7a 100644
--- a/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java
+++ b/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java
@@ -9,21 +9,22 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-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.profile.art.ArtProfileBuilderUtils.SyntheticToSyntheticContextGeneralization;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.startup.profile.ExternalStartupClass;
+import com.android.tools.r8.startup.profile.ExternalStartupItem;
+import com.android.tools.r8.startup.profile.ExternalStartupMethod;
 import com.android.tools.r8.startup.utils.StartupTestingUtils;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.MethodReferenceUtils;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Path;
-import java.util.ArrayList;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Set;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -48,7 +49,9 @@
   @Test
   public void test() throws Exception {
     Path out = temp.newFolder().toPath().resolve("out.txt").toAbsolutePath();
-    List<StartupItem<ClassReference, MethodReference, ?>> startupList = new ArrayList<>();
+    Set<ExternalStartupItem> startupList = new LinkedHashSet<>();
+    SyntheticToSyntheticContextGeneralization syntheticGeneralization =
+        SyntheticToSyntheticContextGeneralization.createForD8();
     testForD8(parameters.getBackend())
         .addInnerClasses(getClass())
         .applyIf(
@@ -65,8 +68,11 @@
         .run(parameters.getRuntime(), Main.class, Boolean.toString(logcat), out.toString())
         .applyIf(
             logcat,
-            StartupTestingUtils.removeStartupListFromStdout(startupList::add),
-            runResult -> StartupTestingUtils.readStartupListFromFile(out, startupList::add))
+            StartupTestingUtils.removeStartupListFromStdout(
+                startupList::add, syntheticGeneralization),
+            runResult ->
+                StartupTestingUtils.readStartupListFromFile(
+                    out, startupList::add, syntheticGeneralization))
         .assertSuccessWithOutputLines(getExpectedOutput());
     assertEquals(getExpectedStartupList(), startupList);
   }
@@ -75,19 +81,18 @@
     return ImmutableList.of("foo");
   }
 
-  private List<StartupItem<ClassReference, MethodReference, ?>> getExpectedStartupList()
-      throws NoSuchMethodException {
-    return ImmutableList.of(
-        StartupClass.referenceBuilder()
+  private Set<ExternalStartupItem> getExpectedStartupList() throws NoSuchMethodException {
+    return ImmutableSet.of(
+        ExternalStartupClass.builder()
             .setClassReference(Reference.classFromClass(Main.class))
             .build(),
-        StartupMethod.referenceBuilder()
+        ExternalStartupMethod.builder()
             .setMethodReference(MethodReferenceUtils.mainMethod(Main.class))
             .build(),
-        StartupClass.referenceBuilder()
+        ExternalStartupClass.builder()
             .setClassReference(Reference.classFromClass(AStartupClass.class))
             .build(),
-        StartupMethod.referenceBuilder()
+        ExternalStartupMethod.builder()
             .setMethodReference(
                 Reference.methodFromMethod(AStartupClass.class.getDeclaredMethod("foo")))
             .build());
diff --git a/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java b/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java
index 179810b..42ff835 100644
--- a/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java
+++ b/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java
@@ -16,14 +16,15 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
-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.graph.DexProgramClass;
 import com.android.tools.r8.ir.desugar.LambdaClass;
+import com.android.tools.r8.profile.art.ArtProfileBuilderUtils.SyntheticToSyntheticContextGeneralization;
 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.profile.ExternalStartupClass;
+import com.android.tools.r8.startup.profile.ExternalStartupItem;
+import com.android.tools.r8.startup.profile.ExternalStartupMethod;
+import com.android.tools.r8.startup.profile.ExternalSyntheticStartupMethod;
 import com.android.tools.r8.startup.utils.MixedSectionLayoutInspector;
 import com.android.tools.r8.startup.utils.StartupTestingUtils;
 import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
@@ -34,10 +35,12 @@
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import java.nio.file.Path;
-import java.util.ArrayList;
 import java.util.Collection;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.function.Consumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -90,7 +93,7 @@
     Path optimizedApp = r8CompileResult.writeToZip();
 
     // Then instrument the app to generate a startup list for the minified app.
-    List<StartupItem<ClassReference, MethodReference, ?>> startupList = new ArrayList<>();
+    Set<ExternalStartupItem> startupList = new LinkedHashSet<>();
     testForD8(parameters.getBackend())
         .addProgramFiles(optimizedApp)
         .apply(
@@ -100,7 +103,9 @@
         .compile()
         .addRunClasspathFiles(StartupTestingUtils.getAndroidUtilLog(temp))
         .run(parameters.getRuntime(), Main.class, Boolean.toString(useLambda))
-        .apply(StartupTestingUtils.removeStartupListFromStdout(startupList::add))
+        .apply(
+            StartupTestingUtils.removeStartupListFromStdout(
+                startupList::add, SyntheticToSyntheticContextGeneralization.createForD8()))
         .assertSuccessWithOutputLines(getExpectedOutput())
         .apply(
             runResult ->
@@ -125,7 +130,7 @@
   @Test
   public void testLayoutUsingR8() throws Exception {
     // First generate a startup list for the original app.
-    List<StartupItem<ClassReference, MethodReference, ?>> startupList = new ArrayList<>();
+    Set<ExternalStartupItem> startupList = new LinkedHashSet<>();
     D8TestCompileResult instrumentationCompileResult =
         testForD8(parameters.getBackend())
             .addInnerClasses(getClass())
@@ -139,7 +144,9 @@
     instrumentationCompileResult
         .addRunClasspathFiles(StartupTestingUtils.getAndroidUtilLog(temp))
         .run(parameters.getRuntime(), Main.class, Boolean.toString(useLambda))
-        .apply(StartupTestingUtils.removeStartupListFromStdout(startupList::add))
+        .apply(
+            StartupTestingUtils.removeStartupListFromStdout(
+                startupList::add, SyntheticToSyntheticContextGeneralization.createForR8()))
         .assertSuccessWithOutputLines(getExpectedOutput())
         .apply(
             runResult ->
@@ -167,7 +174,7 @@
   private void configureStartupOptions(
       TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder,
       CodeInspector inspector,
-      List<StartupItem<ClassReference, MethodReference, ?>> startupList) {
+      Collection<ExternalStartupItem> startupList) {
     testBuilder
         .addOptionsModification(
             options -> {
@@ -188,36 +195,30 @@
   }
 
   @SuppressWarnings("unchecked")
-  private List<StartupItem<ClassReference, MethodReference, ?>> getExpectedStartupList(
+  private Set<ExternalStartupItem> getExpectedStartupList(
       CodeInspector inspector, boolean isStartupListForOriginalApp) throws NoSuchMethodException {
-    ImmutableList.Builder<StartupItem<ClassReference, MethodReference, ?>> builder =
-        ImmutableList.builder();
+    ImmutableSet.Builder<ExternalStartupItem> builder = ImmutableSet.builder();
     builder.add(
-        StartupClass.referenceBuilder()
+        ExternalStartupClass.builder()
             .setClassReference(Reference.classFromClass(Main.class))
             .build(),
-        StartupMethod.referenceBuilder()
+        ExternalStartupMethod.builder()
             .setMethodReference(MethodReferenceUtils.mainMethod(Main.class))
             .build(),
-        StartupClass.referenceBuilder()
-            .setClassReference(Reference.classFromClass(A.class))
-            .build(),
-        StartupMethod.referenceBuilder()
+        ExternalStartupClass.builder().setClassReference(Reference.classFromClass(A.class)).build(),
+        ExternalStartupMethod.builder()
             .setMethodReference(Reference.methodFromMethod(A.class.getDeclaredMethod("a")))
             .build(),
-        StartupClass.referenceBuilder()
-            .setClassReference(Reference.classFromClass(B.class))
-            .build(),
-        StartupMethod.referenceBuilder()
+        ExternalStartupClass.builder().setClassReference(Reference.classFromClass(B.class)).build(),
+        ExternalStartupMethod.builder()
             .setMethodReference(
                 Reference.methodFromMethod(B.class.getDeclaredMethod("b", boolean.class)))
             .build());
     if (useLambda) {
       if (isStartupListForOriginalApp) {
         builder.add(
-            StartupClass.referenceBuilder()
-                .setClassReference(Reference.classFromClass(B.class))
-                .setSynthetic()
+            ExternalSyntheticStartupMethod.builder()
+                .setSyntheticContextReference(Reference.classFromClass(B.class))
                 .build());
       } else {
         ClassSubject bClassSubject = inspector.clazz(B.class);
@@ -238,14 +239,14 @@
             externalSyntheticLambdaClassSubject.getFinalReference();
 
         builder.add(
-            StartupClass.referenceBuilder()
+            ExternalStartupClass.builder()
                 .setClassReference(externalSyntheticLambdaClassReference)
                 .build(),
-            StartupMethod.referenceBuilder()
+            ExternalStartupMethod.builder()
                 .setMethodReference(
                     MethodReferenceUtils.instanceConstructor(externalSyntheticLambdaClassReference))
                 .build(),
-            StartupMethod.referenceBuilder()
+            ExternalStartupMethod.builder()
                 .setMethodReference(
                     Reference.method(
                         externalSyntheticLambdaClassReference,
@@ -253,7 +254,7 @@
                         ImmutableList.of(Reference.classFromClass(Object.class)),
                         null))
                 .build(),
-            StartupMethod.referenceBuilder()
+            ExternalStartupMethod.builder()
                 .setMethodReference(
                     Reference.method(
                         Reference.classFromClass(B.class),
@@ -263,16 +264,14 @@
                 .build());
       }
       builder.add(
-          StartupMethod.referenceBuilder()
+          ExternalStartupMethod.builder()
               .setMethodReference(
                   Reference.methodFromMethod(B.class.getDeclaredMethod("lambda$b$0", Object.class)))
               .build());
     }
     builder.add(
-        StartupClass.referenceBuilder()
-            .setClassReference(Reference.classFromClass(C.class))
-            .build(),
-        StartupMethod.referenceBuilder()
+        ExternalStartupClass.builder().setClassReference(Reference.classFromClass(C.class)).build(),
+        ExternalStartupMethod.builder()
             .setMethodReference(Reference.methodFromMethod(C.class.getDeclaredMethod("c")))
             .build());
     return builder.build();
diff --git a/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java b/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java
index b9ceea2..2b47d1d 100644
--- a/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java
+++ b/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java
@@ -14,13 +14,14 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-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.graph.DexProgramClass;
+import com.android.tools.r8.profile.art.ArtProfileBuilderUtils.SyntheticToSyntheticContextGeneralization;
 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.profile.ExternalStartupClass;
+import com.android.tools.r8.startup.profile.ExternalStartupItem;
+import com.android.tools.r8.startup.profile.ExternalStartupMethod;
+import com.android.tools.r8.startup.profile.ExternalSyntheticStartupMethod;
 import com.android.tools.r8.startup.utils.MixedSectionLayoutInspector;
 import com.android.tools.r8.startup.utils.StartupTestingUtils;
 import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
@@ -29,9 +30,11 @@
 import com.android.tools.r8.utils.MethodReferenceUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
+import com.google.common.collect.ImmutableSet;
 import java.util.Collection;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Set;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -61,7 +64,7 @@
 
   @Test
   public void test() throws Exception {
-    List<StartupItem<ClassReference, MethodReference, ?>> startupList = new ArrayList<>();
+    LinkedHashSet<ExternalStartupItem> startupList = new LinkedHashSet<>();
     testForD8(parameters.getBackend())
         .addInnerClasses(getClass())
         .apply(
@@ -71,7 +74,9 @@
         .compile()
         .addRunClasspathFiles(StartupTestingUtils.getAndroidUtilLog(temp))
         .run(parameters.getRuntime(), Main.class)
-        .apply(StartupTestingUtils.removeStartupListFromStdout(startupList::add))
+        .apply(
+            StartupTestingUtils.removeStartupListFromStdout(
+                startupList::add, SyntheticToSyntheticContextGeneralization.createForR8()))
         .assertSuccessWithOutputLines(getExpectedOutput());
     assertEquals(getExpectedStartupList(), startupList);
 
@@ -101,38 +106,30 @@
     return ImmutableList.of("A", "B", "C");
   }
 
-  private List<StartupItem<ClassReference, MethodReference, ?>> getExpectedStartupList()
-      throws NoSuchMethodException {
-    return ImmutableList.of(
-        StartupClass.referenceBuilder()
+  private Set<ExternalStartupItem> getExpectedStartupList() throws NoSuchMethodException {
+    return ImmutableSet.of(
+        ExternalStartupClass.builder()
             .setClassReference(Reference.classFromClass(Main.class))
             .build(),
-        StartupMethod.referenceBuilder()
+        ExternalStartupMethod.builder()
             .setMethodReference(MethodReferenceUtils.mainMethod(Main.class))
             .build(),
-        StartupClass.referenceBuilder()
-            .setClassReference(Reference.classFromClass(A.class))
-            .build(),
-        StartupMethod.referenceBuilder()
+        ExternalStartupClass.builder().setClassReference(Reference.classFromClass(A.class)).build(),
+        ExternalStartupMethod.builder()
             .setMethodReference(Reference.methodFromMethod(A.class.getDeclaredMethod("a")))
             .build(),
-        StartupClass.referenceBuilder()
-            .setClassReference(Reference.classFromClass(B.class))
-            .build(),
-        StartupMethod.referenceBuilder()
+        ExternalStartupClass.builder().setClassReference(Reference.classFromClass(B.class)).build(),
+        ExternalStartupMethod.builder()
             .setMethodReference(Reference.methodFromMethod(B.class.getDeclaredMethod("b")))
             .build(),
-        StartupClass.referenceBuilder()
-            .setClassReference(Reference.classFromClass(B.class))
-            .setSynthetic()
+        ExternalSyntheticStartupMethod.builder()
+            .setSyntheticContextReference(Reference.classFromClass(B.class))
             .build(),
-        StartupMethod.referenceBuilder()
+        ExternalStartupMethod.builder()
             .setMethodReference(Reference.methodFromMethod(B.class.getDeclaredMethod("lambda$b$0")))
             .build(),
-        StartupClass.referenceBuilder()
-            .setClassReference(Reference.classFromClass(C.class))
-            .build(),
-        StartupMethod.referenceBuilder()
+        ExternalStartupClass.builder().setClassReference(Reference.classFromClass(C.class)).build(),
+        ExternalStartupMethod.builder()
             .setMethodReference(Reference.methodFromMethod(C.class.getDeclaredMethod("c")))
             .build());
   }
diff --git a/src/test/java/com/android/tools/r8/startup/diagnostic/MissingStartupProfileItemsDiagnosticTest.java b/src/test/java/com/android/tools/r8/startup/diagnostic/MissingStartupProfileItemsDiagnosticTest.java
new file mode 100644
index 0000000..60cf897
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/diagnostic/MissingStartupProfileItemsDiagnosticTest.java
@@ -0,0 +1,110 @@
+// 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.diagnostic;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.equalTo;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.ClassReference;
+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.AndroidApiLevel;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Collection;
+import java.util.Collections;
+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 MissingStartupProfileItemsDiagnosticTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(Backend.DEX)
+        .addProgramClasses(Main.class)
+        .addStartupProfileProviders(getStartupProfileProviders())
+        .release()
+        .setIntermediate(true)
+        .setMinApi(AndroidApiLevel.LATEST)
+        .compileWithExpectedDiagnostics(this::inspectDiagnostics);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(Backend.DEX)
+        .addProgramClasses(Main.class)
+        .addKeepMainRule(Main.class)
+        .addStartupProfileProviders(getStartupProfileProviders())
+        .allowDiagnosticWarningMessages()
+        .setMinApi(AndroidApiLevel.LATEST)
+        .compileWithExpectedDiagnostics(this::inspectDiagnostics);
+  }
+
+  private static Collection<StartupProfileProvider> getStartupProfileProviders() {
+    StartupProfileProvider startupProfileProvider =
+        new StartupProfileProvider() {
+          @Override
+          public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) {
+            ClassReference fooClassReference = Reference.classFromTypeName("Foo");
+            ClassReference barClassReference = Reference.classFromTypeName("Bar");
+            ClassReference bazClassReference = Reference.classFromTypeName("Baz");
+            startupProfileBuilder
+                .addStartupClass(
+                    startupClassBuilder -> startupClassBuilder.setClassReference(fooClassReference))
+                .addStartupMethod(
+                    startupMethodBuilder ->
+                        startupMethodBuilder.setMethodReference(
+                            MethodReferenceUtils.mainMethod(barClassReference)))
+                .addSyntheticStartupMethod(
+                    syntheticStartupMethodBuilder ->
+                        syntheticStartupMethodBuilder.setSyntheticContextReference(
+                            bazClassReference));
+          }
+
+          @Override
+          public Origin getOrigin() {
+            return Origin.unknown();
+          }
+        };
+    return Collections.singleton(startupProfileProvider);
+  }
+
+  private void inspectDiagnostics(TestDiagnosticMessages diagnostics) {
+    diagnostics.assertWarningsMatch(
+        allOf(
+            diagnosticType(MissingStartupProfileItemsDiagnostic.class),
+            diagnosticMessage(
+                equalTo(
+                    StringUtils.joinLines(
+                        "Startup method not found: void Bar.main(java.lang.String[])",
+                        "Startup class not found: Baz",
+                        "Startup class not found: Foo")))));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/startup/dump/DumpStartupProfileProvidersTest.java b/src/test/java/com/android/tools/r8/startup/dump/DumpStartupProfileProvidersTest.java
new file mode 100644
index 0000000..edec3b4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/dump/DumpStartupProfileProvidersTest.java
@@ -0,0 +1,166 @@
+// 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.dump;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+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.AndroidApiLevel;
+import com.android.tools.r8.utils.DumpInputFlags;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+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 DumpStartupProfileProvidersTest extends TestBase {
+
+  private enum DumpStrategy {
+    DIRECTORY,
+    FILE;
+
+    DumpInputFlags createDumpInputFlags(Path dump) {
+      if (this == DIRECTORY) {
+        return DumpInputFlags.dumpToDirectory(dump);
+      }
+      assert this == FILE;
+      return DumpInputFlags.dumpToFile(dump);
+    }
+
+    Path createDumpPath(TemporaryFolder temp) throws IOException {
+      if (this == DIRECTORY) {
+        return temp.newFolder().toPath();
+      }
+      assert this == FILE;
+      return temp.newFile("dump.zip").toPath();
+    }
+  }
+
+  @Parameter(0)
+  public DumpStrategy dumpStrategy;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, {0}")
+  public static List<Object[]> data() {
+    return buildParameters(DumpStrategy.values(), getTestParameters().withNoneRuntime().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    Path dump = dumpStrategy.createDumpPath(temp);
+    DumpInputFlags dumpInputFlags = dumpStrategy.createDumpInputFlags(dump);
+    try {
+      testForR8(Backend.DEX)
+          .addProgramClasses(Main.class)
+          .addKeepMainRule(Main.class)
+          .addOptionsModification(options -> options.setDumpInputFlags(dumpInputFlags))
+          .addStartupProfileProviders(getStartupProfileProviders())
+          .allowDiagnosticInfoMessages()
+          .setMinApi(AndroidApiLevel.LATEST)
+          .compileWithExpectedDiagnostics(
+              diagnostics -> {
+                if (dumpInputFlags.shouldFailCompilation()) {
+                  diagnostics.assertErrorsMatch(
+                      diagnosticMessage(containsString("Dumped compilation inputs to:")));
+                } else {
+                  diagnostics.assertInfosMatch(
+                      diagnosticMessage(containsString("Dumped compilation inputs to:")));
+                }
+              });
+      assertFalse("Expected compilation to fail", dumpInputFlags.shouldFailCompilation());
+    } catch (CompilationFailedException e) {
+      assertTrue("Expected compilation to succeed", dumpInputFlags.shouldFailCompilation());
+    }
+    verifyDump(dump);
+  }
+
+  private Collection<StartupProfileProvider> getStartupProfileProviders() {
+    return ImmutableList.of(
+        new StartupProfileProvider() {
+
+          @Override
+          public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) {
+            startupProfileBuilder.addStartupClass(
+                startupClassBuilder ->
+                    startupClassBuilder.setClassReference(Reference.classFromClass(Main.class)));
+          }
+
+          @Override
+          public Origin getOrigin() {
+            return Origin.unknown();
+          }
+        },
+        new StartupProfileProvider() {
+
+          @Override
+          public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) {
+            startupProfileBuilder.addStartupMethod(
+                startupMethodBuilder ->
+                    startupMethodBuilder.setMethodReference(
+                        MethodReferenceUtils.mainMethod(Main.class)));
+          }
+
+          @Override
+          public Origin getOrigin() {
+            return Origin.unknown();
+          }
+        });
+  }
+
+  private void verifyDump(Path dump) throws IOException {
+    if (dumpStrategy == DumpStrategy.DIRECTORY) {
+      List<Path> dumps =
+          Files.walk(dump, 1).filter(path -> path.toFile().isFile()).collect(Collectors.toList());
+      assertEquals(1, dumps.size());
+      dump = dumps.get(0);
+    }
+
+    assertTrue(Files.exists(dump));
+    Path unzipped = temp.newFolder().toPath();
+    ZipUtils.unzip(dump.toString(), unzipped.toFile());
+
+    Path startupProfile1 = unzipped.resolve("startup-profile-1.txt");
+    assertTrue(Files.exists(startupProfile1));
+    assertEquals(
+        Lists.newArrayList(Reference.classFromClass(Main.class).getDescriptor()),
+        FileUtils.readAllLines(startupProfile1));
+
+    Path startupProfile2 = unzipped.resolve("startup-profile-2.txt");
+    assertTrue(Files.exists(startupProfile2));
+    assertEquals(
+        Lists.newArrayList(
+            MethodReferenceUtils.toSmaliString(MethodReferenceUtils.mainMethod(Main.class))),
+        FileUtils.readAllLines(startupProfile2));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/startup/profile/ExternalStartupClass.java b/src/test/java/com/android/tools/r8/startup/profile/ExternalStartupClass.java
new file mode 100644
index 0000000..4eee018
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/profile/ExternalStartupClass.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.startup.profile;
+
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.startup.StartupClassBuilder;
+import java.util.function.Function;
+
+public class ExternalStartupClass extends ExternalStartupItem {
+
+  private final ClassReference classReference;
+
+  ExternalStartupClass(ClassReference classReference) {
+    this.classReference = classReference;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  public <T> T apply(
+      Function<ExternalStartupClass, T> classFunction,
+      Function<ExternalStartupMethod, T> methodFunction,
+      Function<ExternalSyntheticStartupMethod, T> syntheticMethodFunction) {
+    return classFunction.apply(this);
+  }
+
+  public ClassReference getReference() {
+    return classReference;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    ExternalStartupClass that = (ExternalStartupClass) o;
+    return classReference.equals(that.classReference);
+  }
+
+  @Override
+  public int hashCode() {
+    return classReference.hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return classReference.getTypeName();
+  }
+
+  public static class Builder implements StartupClassBuilder {
+
+    private ClassReference classReference;
+
+    @Override
+    public Builder setClassReference(ClassReference classReference) {
+      this.classReference = classReference;
+      return this;
+    }
+
+    public ExternalStartupClass build() {
+      return new ExternalStartupClass(classReference);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/startup/profile/ExternalStartupItem.java b/src/test/java/com/android/tools/r8/startup/profile/ExternalStartupItem.java
new file mode 100644
index 0000000..e3aa745
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/profile/ExternalStartupItem.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.profile;
+
+import java.util.function.Function;
+
+public abstract class ExternalStartupItem {
+
+  public abstract <T> T apply(
+      Function<ExternalStartupClass, T> classFunction,
+      Function<ExternalStartupMethod, T> methodFunction,
+      Function<ExternalSyntheticStartupMethod, T> syntheticMethodFunction);
+}
diff --git a/src/test/java/com/android/tools/r8/startup/profile/ExternalStartupMethod.java b/src/test/java/com/android/tools/r8/startup/profile/ExternalStartupMethod.java
new file mode 100644
index 0000000..cda7ff7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/profile/ExternalStartupMethod.java
@@ -0,0 +1,73 @@
+// 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.profile;
+
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.startup.StartupMethodBuilder;
+import com.android.tools.r8.startup.profile.ExternalStartupClass.Builder;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import java.util.function.Function;
+
+public class ExternalStartupMethod extends ExternalStartupItem {
+
+  private final MethodReference methodReference;
+
+  ExternalStartupMethod(MethodReference methodReference) {
+    this.methodReference = methodReference;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  public <T> T apply(
+      Function<ExternalStartupClass, T> classFunction,
+      Function<ExternalStartupMethod, T> methodFunction,
+      Function<ExternalSyntheticStartupMethod, T> syntheticMethodFunction) {
+    return methodFunction.apply(this);
+  }
+
+  public MethodReference getReference() {
+    return methodReference;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    ExternalStartupMethod that = (ExternalStartupMethod) o;
+    return methodReference.equals(that.methodReference);
+  }
+
+  @Override
+  public int hashCode() {
+    return methodReference.hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return MethodReferenceUtils.toSourceString(methodReference);
+  }
+
+  public static class Builder implements StartupMethodBuilder {
+
+    private MethodReference methodReference;
+
+    @Override
+    public Builder setMethodReference(MethodReference methodReference) {
+      this.methodReference = methodReference;
+      return this;
+    }
+
+    public ExternalStartupMethod build() {
+      return new ExternalStartupMethod(methodReference);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/startup/profile/ExternalSyntheticStartupMethod.java b/src/test/java/com/android/tools/r8/startup/profile/ExternalSyntheticStartupMethod.java
new file mode 100644
index 0000000..6d07638
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/profile/ExternalSyntheticStartupMethod.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.startup.profile;
+
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.startup.SyntheticStartupMethodBuilder;
+import java.util.function.Function;
+
+public class ExternalSyntheticStartupMethod extends ExternalStartupItem {
+
+  private final ClassReference syntheticContextReference;
+
+  ExternalSyntheticStartupMethod(ClassReference syntheticContextReference) {
+    this.syntheticContextReference = syntheticContextReference;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  public <T> T apply(
+      Function<ExternalStartupClass, T> classFunction,
+      Function<ExternalStartupMethod, T> methodFunction,
+      Function<ExternalSyntheticStartupMethod, T> syntheticMethodFunction) {
+    return syntheticMethodFunction.apply(this);
+  }
+
+  public ClassReference getSyntheticContextReference() {
+    return syntheticContextReference;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    ExternalSyntheticStartupMethod that = (ExternalSyntheticStartupMethod) o;
+    return syntheticContextReference.equals(that.syntheticContextReference);
+  }
+
+  @Override
+  public int hashCode() {
+    return syntheticContextReference.hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return "S(" + syntheticContextReference.getTypeName() + ")";
+  }
+
+  public static class Builder implements SyntheticStartupMethodBuilder {
+
+    private ClassReference syntheticContextReference;
+
+    @Override
+    public Builder setSyntheticContextReference(ClassReference syntheticContextReference) {
+      this.syntheticContextReference = syntheticContextReference;
+      return this;
+    }
+
+    public ExternalSyntheticStartupMethod build() {
+      return new ExternalSyntheticStartupMethod(syntheticContextReference);
+    }
+  }
+}
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 ad43216..691a33d 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
@@ -5,38 +5,43 @@
 package com.android.tools.r8.startup.utils;
 
 import static com.android.tools.r8.TestBase.transformer;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.D8TestBuilder;
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TextInputStream;
 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;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.startup.instrumentation.StartupInstrumentationOptions;
-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.profile.art.AlwaysTrueArtProfileRulePredicate;
+import com.android.tools.r8.profile.art.ArtProfileBuilder;
+import com.android.tools.r8.profile.art.ArtProfileBuilderUtils;
+import com.android.tools.r8.profile.art.ArtProfileBuilderUtils.SyntheticToSyntheticContextGeneralization;
+import com.android.tools.r8.profile.art.HumanReadableArtProfileParser;
+import com.android.tools.r8.profile.art.HumanReadableArtProfileParserBuilder;
+import com.android.tools.r8.startup.StartupClassBuilder;
+import com.android.tools.r8.startup.StartupMethodBuilder;
 import com.android.tools.r8.startup.StartupProfileBuilder;
 import com.android.tools.r8.startup.StartupProfileProvider;
+import com.android.tools.r8.startup.SyntheticStartupMethodBuilder;
+import com.android.tools.r8.startup.profile.ExternalStartupClass;
+import com.android.tools.r8.startup.profile.ExternalStartupItem;
+import com.android.tools.r8.startup.profile.ExternalStartupMethod;
+import com.android.tools.r8.startup.profile.ExternalSyntheticStartupMethod;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.ClassReferenceUtils;
-import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.UTF8TextInputStream;
 import java.io.IOException;
-import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.List;
+import java.util.Collection;
 import java.util.function.Consumer;
 import org.junit.rules.TemporaryFolder;
 
@@ -44,51 +49,74 @@
 
   private static String startupInstrumentationTag = "startup";
 
-  private enum AppVariant {
-    ORIGINAL,
-    OPTIMIZED;
+  private static ArtProfileBuilder createStartupItemFactory(
+      Consumer<ExternalStartupItem> startupItemConsumer,
+      SyntheticToSyntheticContextGeneralization syntheticToSyntheticContextGeneralization) {
+    StartupProfileBuilder startupProfileBuilder =
+        new StartupProfileBuilder() {
+          @Override
+          public StartupProfileBuilder addStartupClass(
+              Consumer<StartupClassBuilder> startupClassBuilderConsumer) {
+            ExternalStartupClass.Builder startupClassBuilder = ExternalStartupClass.builder();
+            startupClassBuilderConsumer.accept(startupClassBuilder);
+            startupItemConsumer.accept(startupClassBuilder.build());
+            return this;
+          }
 
-    boolean isOriginal() {
-      return this == ORIGINAL;
-    }
+          @Override
+          public StartupProfileBuilder addStartupMethod(
+              Consumer<StartupMethodBuilder> startupMethodBuilderConsumer) {
+            ExternalStartupMethod.Builder startupMethodBuilder = ExternalStartupMethod.builder();
+            startupMethodBuilderConsumer.accept(startupMethodBuilder);
+            startupItemConsumer.accept(startupMethodBuilder.build());
+            return this;
+          }
+
+          @Override
+          public StartupProfileBuilder addSyntheticStartupMethod(
+              Consumer<SyntheticStartupMethodBuilder> syntheticStartupMethodBuilderConsumer) {
+            ExternalSyntheticStartupMethod.Builder syntheticStartupMethodBuilder =
+                ExternalSyntheticStartupMethod.builder();
+            syntheticStartupMethodBuilderConsumer.accept(syntheticStartupMethodBuilder);
+            startupItemConsumer.accept(syntheticStartupMethodBuilder.build());
+            return this;
+          }
+
+          @Override
+          public StartupProfileBuilder addHumanReadableArtProfile(
+              TextInputStream textInputStream,
+              Consumer<HumanReadableArtProfileParserBuilder> parserBuilderConsumer) {
+            throw new Unreachable();
+          }
+        };
+    return ArtProfileBuilderUtils.createBuilderForArtProfileToStartupProfileConversion(
+        startupProfileBuilder,
+        new AlwaysTrueArtProfileRulePredicate(),
+        syntheticToSyntheticContextGeneralization);
   }
 
   public static ThrowableConsumer<D8TestBuilder>
       enableStartupInstrumentationForOriginalAppUsingFile(TestParameters parameters) {
-    return testBuilder ->
-        enableStartupInstrumentation(testBuilder, parameters, AppVariant.ORIGINAL, false);
+    return testBuilder -> enableStartupInstrumentation(testBuilder, parameters, false);
   }
 
   public static ThrowableConsumer<D8TestBuilder>
       enableStartupInstrumentationForOriginalAppUsingLogcat(TestParameters parameters) {
-    return testBuilder ->
-        enableStartupInstrumentation(testBuilder, parameters, AppVariant.ORIGINAL, true);
-  }
-
-  public static ThrowableConsumer<D8TestBuilder>
-      enableStartupInstrumentationForOptimizedAppUsingFile(TestParameters parameters) {
-    return testBuilder ->
-        enableStartupInstrumentation(testBuilder, parameters, AppVariant.OPTIMIZED, false);
+    return testBuilder -> enableStartupInstrumentation(testBuilder, parameters, true);
   }
 
   public static ThrowableConsumer<D8TestBuilder>
       enableStartupInstrumentationForOptimizedAppUsingLogcat(TestParameters parameters) {
-    return testBuilder ->
-        enableStartupInstrumentation(testBuilder, parameters, AppVariant.OPTIMIZED, true);
+    return testBuilder -> enableStartupInstrumentation(testBuilder, parameters, true);
   }
 
   private static void enableStartupInstrumentation(
-      D8TestBuilder testBuilder, TestParameters parameters, AppVariant appVariant, boolean logcat)
-      throws IOException {
+      D8TestBuilder testBuilder, TestParameters parameters, boolean logcat) throws IOException {
     testBuilder
         .addOptionsModification(
             options -> {
               StartupInstrumentationOptions startupInstrumentationOptions =
-                  options
-                      .getStartupInstrumentationOptions()
-                      .setEnableStartupInstrumentation()
-                      .setEnableGeneralizationOfSyntheticsToSyntheticContext(
-                          appVariant.isOriginal());
+                  options.getStartupInstrumentationOptions().setEnableStartupInstrumentation();
               if (logcat) {
                 startupInstrumentationOptions.setStartupInstrumentationTag(
                     startupInstrumentationTag);
@@ -108,94 +136,93 @@
   }
 
   public static void readStartupListFromFile(
-      Path path, Consumer<StartupItem<ClassReference, MethodReference, ?>> startupItemConsumer)
+      Path path,
+      Consumer<ExternalStartupItem> startupItemConsumer,
+      SyntheticToSyntheticContextGeneralization syntheticToSyntheticContextGeneralization)
       throws IOException {
-    StartupConfigurationParser.createReferenceParser()
-        .parseLines(
-            Files.readAllLines(path),
-            startupItemConsumer,
-            startupItemConsumer,
-            error -> fail("Unexpected parse error: " + error));
+    TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
+    HumanReadableArtProfileParser parser =
+        HumanReadableArtProfileParser.builder()
+            .setReporter(new Reporter(diagnostics))
+            .setProfileBuilder(
+                createStartupItemFactory(
+                    startupItemConsumer, syntheticToSyntheticContextGeneralization))
+            .build();
+    parser.parse(new UTF8TextInputStream(path), Origin.unknown());
+    diagnostics.assertNoMessages();
   }
 
   public static ThrowingConsumer<D8TestRunResult, RuntimeException> removeStartupListFromStdout(
-      Consumer<StartupItem<ClassReference, MethodReference, ?>> startupItemConsumer) {
-    return runResult -> removeStartupListFromStdout(runResult, startupItemConsumer);
+      Consumer<ExternalStartupItem> startupItemConsumer,
+      SyntheticToSyntheticContextGeneralization syntheticToSyntheticContextGeneralization) {
+    return runResult ->
+        removeStartupListFromStdout(
+            runResult, startupItemConsumer, syntheticToSyntheticContextGeneralization);
   }
 
   public static void removeStartupListFromStdout(
       D8TestRunResult runResult,
-      Consumer<StartupItem<ClassReference, MethodReference, ?>> startupItemConsumer) {
-    StartupConfigurationParser<ClassReference, MethodReference, TypeReference> parser =
-        StartupConfigurationParser.createReferenceParser();
+      Consumer<ExternalStartupItem> startupItemConsumer,
+      SyntheticToSyntheticContextGeneralization syntheticToSyntheticContextGeneralization) {
+    TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
+    HumanReadableArtProfileParser parser =
+        HumanReadableArtProfileParser.builder()
+            .setReporter(new Reporter(diagnostics))
+            .setProfileBuilder(
+                createStartupItemFactory(
+                    startupItemConsumer, syntheticToSyntheticContextGeneralization))
+            .build();
     StringBuilder stdoutBuilder = new StringBuilder();
     String startupDescriptorPrefix = "[" + startupInstrumentationTag + "] ";
     for (String line : StringUtils.splitLines(runResult.getStdOut(), true)) {
       if (line.startsWith(startupDescriptorPrefix)) {
         String message = line.substring(startupDescriptorPrefix.length());
-        parser.parseLine(
-            message,
-            startupItemConsumer,
-            startupItemConsumer,
-            error -> fail("Unexpected parse error: " + error));
+        assertTrue(parser.parseRule(message));
       } else {
         stdoutBuilder.append(line).append(System.lineSeparator());
       }
     }
+    diagnostics.assertNoMessages();
     runResult.getResult().setStdout(stdoutBuilder.toString());
   }
 
   public static void setStartupConfiguration(
       TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder,
-      List<StartupItem<ClassReference, MethodReference, ?>> startupItems) {
-    testBuilder.addOptionsModification(
-        options -> {
-          DexItemFactory dexItemFactory = options.dexItemFactory();
-          StartupProfile startupProfile =
-              StartupProfile.builder()
-                  .apply(
-                      builder ->
-                          startupItems.forEach(
-                              startupItem ->
-                                  builder.addStartupItem(
-                                      convertStartupItemToDex(startupItem, dexItemFactory))))
-                  .build();
-          StartupProfileProvider startupProfileProvider =
-              new StartupProfileProvider() {
-                @Override
-                public String get() {
-                  return startupProfile.serializeToString();
-                }
+      Collection<ExternalStartupItem> startupItems) {
+    StartupProfileProvider startupProfileProvider =
+        new StartupProfileProvider() {
+          @Override
+          public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) {
+            for (ExternalStartupItem startupItem : startupItems) {
+              startupItem.apply(
+                  startupClass ->
+                      startupProfileBuilder.addStartupClass(
+                          startupClassBuilder ->
+                              startupClassBuilder.setClassReference(startupClass.getReference())),
+                  startupMethod ->
+                      startupProfileBuilder.addStartupMethod(
+                          startupMethodBuilder ->
+                              startupMethodBuilder.setMethodReference(
+                                  startupMethod.getReference())),
+                  syntheticStartupMethod ->
+                      startupProfileBuilder.addSyntheticStartupMethod(
+                          syntheticStartupMethodBuilder ->
+                              syntheticStartupMethodBuilder.setSyntheticContextReference(
+                                  syntheticStartupMethod.getSyntheticContextReference())));
+            }
+          }
 
-                @Override
-                public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) {
-                  throw new Unimplemented();
-                }
-
-                @Override
-                public Origin getOrigin() {
-                  return Origin.unknown();
-                }
-              };
-          options.getStartupOptions().setStartupProfileProvider(startupProfileProvider);
-        });
-  }
-
-  private static StartupItem<DexType, DexMethod, ?> convertStartupItemToDex(
-      StartupItem<ClassReference, MethodReference, ?> startupItem, DexItemFactory dexItemFactory) {
-    return StartupItem.dexBuilder()
-        .applyIf(
-            startupItem.isStartupClass(),
-            builder ->
-                builder.setClassReference(
-                    ClassReferenceUtils.toDexType(
-                        startupItem.asStartupClass().getReference(), dexItemFactory)),
-            builder ->
-                builder.setMethodReference(
-                    MethodReferenceUtils.toDexMethod(
-                        startupItem.asStartupMethod().getReference(), dexItemFactory)))
-        .setFlags(startupItem.getFlags())
-        .build();
+          @Override
+          public Origin getOrigin() {
+            return Origin.unknown();
+          }
+        };
+    if (testBuilder.isD8TestBuilder()) {
+      testBuilder.asD8TestBuilder().addStartupProfileProviders(startupProfileProvider);
+    } else {
+      assertTrue(testBuilder.isR8TestBuilder());
+      testBuilder.asR8TestBuilder().addStartupProfileProviders(startupProfileProvider);
+    }
   }
 
   private static byte[] getTransformedAndroidUtilLog() throws IOException {
diff --git a/third_party/android_jar/lib-v33.tar.gz.sha1 b/third_party/android_jar/lib-v33.tar.gz.sha1
index 2e88eb6..b4d9b45 100644
--- a/third_party/android_jar/lib-v33.tar.gz.sha1
+++ b/third_party/android_jar/lib-v33.tar.gz.sha1
@@ -1 +1 @@
-ecd236f896f9a19eeb7d46eb983cbaf94fd31d76
\ No newline at end of file
+53e8c839d3ec4de175784dddb64731c3a68f9579
\ No newline at end of file
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 040d696..dc12e2c 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 @@
-98dc48c246bd0855133e138c2cd2b5835cf862cf
\ No newline at end of file
+35ed1547b79e22eb4b933b6deb0f929d7ff8aebc
\ No newline at end of file
diff --git a/tools/archive_desugar_jdk_libs.py b/tools/archive_desugar_jdk_libs.py
index 1762c2c..004450b 100755
--- a/tools/archive_desugar_jdk_libs.py
+++ b/tools/archive_desugar_jdk_libs.py
@@ -135,6 +135,7 @@
   git_utils.GitClone(
     'https://github.com/'
         + github_account + '/' + GITHUB_REPRO, checkout_dir)
+  git_utils.GitCheckout('3a970cd008e944845a7b3d29a3b5a13123df11fe', checkout_dir)
 
 def GetJavaEnv():
   java_env = dict(os.environ, JAVA_HOME = jdk.GetJdk11Home())
@@ -207,7 +208,7 @@
     file.write(hexdigest)
 
 def Undesugar(variant, maven_zip, version, undesugared_maven_zip):
-  gradle.RunGradle(['testJar', 'repackageTestDeps'])
+  gradle.RunGradle(['testJar', 'repackageTestDeps', '-Pno_internal'])
   with utils.TempDir() as tmp:
     with zipfile.ZipFile(maven_zip, 'r') as zip_ref:
       zip_ref.extractall(tmp)
@@ -290,8 +291,8 @@
 
     # 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,
+        utils.get_maven_path(LIBRARY_NAME_MAP[variant], version),
+        '%s-%s.jar' % (LIBRARY_NAME_MAP[variant], version),
         is_main)
     if options.dry_run:
       print('Dry run, not actually creating maven repo')
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 6baaada..8149313 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -189,12 +189,21 @@
       print("Unimplemented: proguard_input configuration.")
 
   def main_dex_list_resource(self):
-    if self.if_exists('main-dex-list.txt'):
-      print("Unimplemented: main-dex-list.")
+    return self.if_exists('main-dex-list.txt')
 
   def main_dex_rules_resource(self):
     return self.if_exists('main-dex-rules.txt')
 
+  def startup_profile_resources(self):
+    startup_profile_resources = []
+    while True:
+      current_startup_profile_index = len(startup_profile_resources) + 1
+      startup_profile_resource = self.if_exists(
+          'startup-profile-%s.txt' % current_startup_profile_index)
+      if startup_profile_resource is None:
+        return startup_profile_resources
+      startup_profile_resources.append(startup_profile_resource)
+
   def build_properties_file(self):
     return self.if_exists('build.properties')
 
@@ -470,8 +479,12 @@
         # -print{mapping,usage}
         clean_config(dump.config_file(), args)
       cmd.extend(['--pg-conf', dump.config_file()])
+    if dump.main_dex_list_resource():
+      cmd.extend(['--main-dex-list', dump.main_dex_list_resource()])
     if dump.main_dex_rules_resource():
       cmd.extend(['--main-dex-rules', dump.main_dex_rules_resource()])
+    for startup_profile_resource in dump.startup_profile_resources():
+      cmd.extend(['--startup-profile', startup_profile_resource])
     if compiler == 'l8':
       if dump.config_file():
         cmd.extend(['--pg-map-output', '%s.map' % out])
diff --git a/tools/create_maven_release.py b/tools/create_maven_release.py
index fc7eafb..7d73a93 100755
--- a/tools/create_maven_release.py
+++ b/tools/create_maven_release.py
@@ -391,13 +391,15 @@
     make_archive(destination, 'zip', tmp_dir)
     move(destination + '.zip', destination)
 
-def convert_desugar_configuration(configuration, machine_configuration):
+def convert_desugar_configuration(
+    configuration, conversions, implementation, machine_configuration):
   cmd = [jdk.GetJavaExecutable(),
       '-cp',
       utils.R8_JAR,
       'com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.DesugaredLibraryConverter',
       configuration,
-      utils.DESUGAR_IMPLEMENTATION_JDK11,
+      implementation,
+      conversions,
       utils.get_android_jar(33),
       machine_configuration]
   subprocess.check_call(cmd)
@@ -410,7 +412,7 @@
 
     if (not version.startswith("1.")):
       machine_configuration = join(tmp_dir, "machine.json")
-      convert_desugar_configuration(configuration, machine_configuration)
+      convert_desugar_configuration(configuration, conversions, implementation, machine_configuration)
       configuration = machine_configuration
 
     # Generate the pom file.
diff --git a/tools/desugar_jdk_libs_repository.py b/tools/desugar_jdk_libs_repository.py
index 4417255..7cb7a19 100755
--- a/tools/desugar_jdk_libs_repository.py
+++ b/tools/desugar_jdk_libs_repository.py
@@ -11,8 +11,10 @@
 import subprocess
 import sys
 
+import gradle
 import utils
 import create_maven_release
+import archive_desugar_jdk_libs
 
 class Variant(Enum):
     jdk8 = 'jdk8'
@@ -114,6 +116,7 @@
       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')
+  gradle.RunGradle([utils.R8])
   with utils.TempDir(delete=False) as tmp_dir:
     (name, version) = utils.desugar_configuration_name_and_version(configuration, False)
     # Checkout desugar_jdk_libs from GitHub
@@ -167,11 +170,24 @@
           '--spawn_strategy=local',
           '--verbose_failures',
           implementation_build_target])
+
+    # Undesugar desugared library if needed.
+    undesugared_if_needed = join(checkout_dir, implementation_build_output)
+    if (args.variant == Variant.jdk11_minimal
+        or args.variant == Variant.jdk11
+        or args.variant == Variant.jdk11_nio):
+      undesugared_if_needed = join(tmp_dir, 'undesugared.zip')
+      archive_desugar_jdk_libs.Undesugar(
+        str(args.variant),
+        join(checkout_dir, implementation_build_output),
+        version,
+        undesugared_if_needed)
+
     unzip_dir = join(tmp_dir, 'desugar_jdk_libs_unzipped')
     cmd = [
         'unzip',
         '-q',
-        join(checkout_dir, implementation_build_output),
+        undesugared_if_needed,
         '-d',
         unzip_dir]
     subprocess.check_call(cmd)
@@ -204,7 +220,7 @@
     print("    changing = true")
     print("  }")
     print()
-    print('If not using the !changing" propertyRemember to run gradle with '
+    print('If not using the "changing" propertyRemember to run gradle with '
       + " --refresh-dependencies (./gradlew --refresh-dependencies ...) "
       + "to ensure the cache is not used when the same version is published."
       + "multiple times.")
diff --git a/tools/git_utils.py b/tools/git_utils.py
index 1ce0a0a..542947a 100644
--- a/tools/git_utils.py
+++ b/tools/git_utils.py
@@ -11,6 +11,12 @@
   utils.PrintCmd(cmd)
   return subprocess.check_call(cmd)
 
+def GitCheckout(revision, checkout_dir):
+  with utils.ChangedWorkingDirectory(checkout_dir):
+    cmd = ['git', 'checkout', revision]
+    utils.PrintCmd(cmd)
+    return subprocess.check_call(cmd)
+
 def GetHeadRevision(checkout_dir, use_main=False):
   revision_from = 'origin/main' if use_main else 'HEAD'
   cmd = ['git', 'rev-parse', revision_from]
diff --git a/tools/r8_release.py b/tools/r8_release.py
index f7f0c15..867d312 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -15,7 +15,7 @@
 
 import utils
 
-R8_DEV_BRANCH = '4.0'
+R8_DEV_BRANCH = '8.0'
 R8_VERSION_FILE = os.path.join(
     'src', 'main', 'java', 'com', 'android', 'tools', 'r8', 'Version.java')
 THIS_FILE_RELATIVE = os.path.join('tools', 'r8_release.py')
@@ -567,12 +567,10 @@
         % library_version)
       sys.exit(1)
 
-    library_archive = DESUGAR_JDK_LIBS + '.zip'
-    library_jar = DESUGAR_JDK_LIBS + '.jar'
-    library_artifact_id = \
-        '%s:%s:%s' % (ANDROID_TOOLS_PACKAGE, DESUGAR_JDK_LIBS, library_version)
-
     postfix = "" if library_version.startswith('1.1') else '_jdk11_legacy'
+    library_archive = DESUGAR_JDK_LIBS + postfix + '.zip'
+    library_jar = DESUGAR_JDK_LIBS + postfix + '.jar'
+
     configuration_archive = DESUGAR_JDK_LIBS_CONFIGURATION + postfix + '.zip'
 
     with utils.TempDir() as temp:
@@ -590,9 +588,11 @@
             args, [library_gfile, configuration_gfile])
 
         print("Staged Release ID " + release_id + ".\n")
+        library_artifact_id = \
+            '%s:%s:%s' % (ANDROID_TOOLS_PACKAGE, DESUGAR_JDK_LIBS, library_version)
         gmaven_publisher_stage_redir_test_info(
             release_id,
-            "com.android.tools:%s:%s" % (DESUGAR_JDK_LIBS, library_version),
+            library_artifact_id,
             library_jar)
 
         print("")
@@ -893,6 +893,10 @@
                       default=[],
                       action='append',
                       help='List of bugs for release version')
+  result.add_argument('--no-bugs',
+                      default=False,
+                      action='store_true',
+                      help='Allow Studio release without specifying any bugs')
   result.add_argument('--studio',
                       metavar=('<path>'),
                       help='Release for studio by setting the path to a studio '
@@ -938,10 +942,15 @@
                       metavar=('<path>'),
                       help='Location for dry run output.')
   args = result.parse_args()
+  if (len(args.bug) > 0 and args.no_bugs):
+    print("Use of '--bug' and '--no-bugs' are mutually exclusive")
+    sys.exit(1)
+
   if (args.studio
       and args.version
       and not 'dev' in args.version
-      and args.bug == []):
+      and args.bug == []
+      and not args.no_bugs):
     print("When releasing a release version to Android Studio add the "
            + "list of bugs by using '--bug'")
     sys.exit(1)
diff --git a/tools/startup/generate_startup_descriptors.py b/tools/startup/generate_startup_descriptors.py
index ee3f27a..d0af556 100755
--- a/tools/startup/generate_startup_descriptors.py
+++ b/tools/startup/generate_startup_descriptors.py
@@ -30,7 +30,7 @@
         profile_classes_and_methods, iteration, options)
     current_startup_descriptors = \
         profile_utils.transform_art_profile_to_r8_startup_list(
-            profile_classes_and_methods)
+            profile_classes_and_methods, options.generalize_synthetics)
   write_tmp_startup_descriptors(current_startup_descriptors, iteration, options)
   new_startup_descriptors = add_r8_startup_descriptors(
       startup_descriptors, current_startup_descriptors)
@@ -307,6 +307,11 @@
   result.add_argument('--device-pin',
                       help='Device pin code (e.g., 1234)',
                       action='append')
+  result.add_argument('--generalize-synthetics',
+                      help='Whether synthetics should be abstracted into their '
+                           'synthetic contexts',
+                      action='store_true',
+                      default=False)
   result.add_argument('--logcat',
                       action='store_true',
                       default=False)
diff --git a/tools/startup/profile_utils.py b/tools/startup/profile_utils.py
index 0e69397..923326f 100755
--- a/tools/startup/profile_utils.py
+++ b/tools/startup/profile_utils.py
@@ -41,11 +41,12 @@
     art_profile[descriptor] = flags
   return art_profile
 
-def transform_art_profile_to_r8_startup_list(art_profile):
+def transform_art_profile_to_r8_startup_list(
+    art_profile, generalize_synthetics=False):
   r8_startup_list = {}
   for startup_descriptor, flags in art_profile.items():
     transformed_startup_descriptor = transform_synthetic_descriptor(
-        startup_descriptor)
+        startup_descriptor) if generalize_synthetics else startup_descriptor
     r8_startup_list[transformed_startup_descriptor] = {
       'conditional_startup': False,
       'post_startup': flags['post_startup'],
diff --git a/tools/startup/relayout.py b/tools/startup/relayout.py
new file mode 100755
index 0000000..6220336
--- /dev/null
+++ b/tools/startup/relayout.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+import argparse
+import os
+import subprocess
+import sys
+
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+import apk_masseur
+import toolhelper
+import utils
+
+def parse_options(argv):
+  result = argparse.ArgumentParser(
+      description='Relayout a given APK using a startup profile.')
+  result.add_argument('--apk',
+                      help='Path to the .apk',
+                      required=True)
+  result.add_argument('--out',
+                      help='Destination of resulting apk',
+                      required=True)
+  result.add_argument('--profile',
+                      help='Path to the startup profile',
+                      required=True)
+  options, args = result.parse_known_args(argv)
+  return options, args
+
+def get_min_api(apk):
+  aapt = os.path.join(utils.getAndroidBuildTools(), 'aapt')
+  cmd = [aapt, 'dump', 'badging', apk]
+  stdout = subprocess.check_output(cmd).decode('utf-8').strip()
+  for line in stdout.splitlines():
+    if line.startswith('sdkVersion:\''):
+      return int(line[len('sdkVersion:\''): -1])
+  raise ValueError('Unexpected stdout: %s' % stdout)
+
+def main(argv):
+  (options, args) = parse_options(argv)
+  with utils.TempDir() as temp:
+    dex = os.path.join(temp, 'dex.zip')
+    d8_args = [
+        '--min-api', str(get_min_api(options.apk)),
+        '--output', dex,
+        '--no-desugaring',
+        '--release',
+        options.apk]
+    extra_args = ['-Dcom.android.tools.r8.startup.profile=%s' % options.profile]
+    toolhelper.run(
+        'd8',
+        d8_args,
+        extra_args=extra_args,
+        main='com.android.tools.r8.D8')
+    apk_masseur.masseur(options.apk, dex=dex, out=options.out)
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/tools/trigger.py b/tools/trigger.py
index d87cad9..d716a55 100755
--- a/tools/trigger.py
+++ b/tools/trigger.py
@@ -23,6 +23,7 @@
 TRIGGERS_RE = r'^  triggers: "(\w.*)"'
 
 DESUGAR_JDK11_BOT = 'lib_desugar-archive-jdk11'
+DESUGAR_JDK11_LEGACY_BOT = 'lib_desugar-archive-jdk11-legacy'
 DESUGAR_JDK8_BOT = 'lib_desugar-archive-jdk8'
 
 def ParseOptions():
@@ -37,6 +38,9 @@
   result.add_option('--desugar-jdk11',
                     help='Run the jdk11 library desugar and archiving bot.',
                     default=False, action='store_true')
+  result.add_option('--desugar-jdk11-legacy',
+                    help='Run the jdk11 legacy library desugar and archiving bot.',
+                    default=False, action='store_true')
   result.add_option('--desugar-jdk8',
                     help='Run the jdk8 library desugar and archiving bot.',
                     default=False, action='store_true')
@@ -66,6 +70,7 @@
           assert 'release' not in builder, builder
           main_builders.append(builder)
   print('Desugar jdk11 builder:\n  ' + DESUGAR_JDK11_BOT)
+  print('Desugar jdk11 legacy builder:\n  ' + DESUGAR_JDK11_LEGACY_BOT)
   print('Desugar jdk8 builder:\n  ' + DESUGAR_JDK8_BOT)
   print('Main builders:\n  ' + '\n  '.join(main_builders))
   print('Release builders:\n  ' + '\n  '.join(release_builders))
@@ -90,7 +95,7 @@
 
 def Main():
   (options, args) = ParseOptions()
-  desugar = options.desugar_jdk11 or options.desugar_jdk8
+  desugar = options.desugar_jdk11 or options.desugar_jdk11_legacy or options.desugar_jdk8
   if len(args) != 1 and not options.cl and not desugar:
     print('Takes exactly one argument, the commit to run')
     return 1
@@ -111,8 +116,13 @@
     assert builder in main_builders or builder in release_builders
     builders = [options.builder]
   if desugar:
-    assert options.desugar_jdk8 or options.desugar_jdk11
-    builders = [DESUGAR_JDK8_BOT if options.desugar_jdk8 else DESUGAR_JDK11_BOT]
+    assert options.desugar_jdk11 or options.desugar_jdk11_legacy or options.desugar_jdk8
+    if options.desugar_jdk11:
+      builders = [DESUGAR_JDK11_BOT]
+    elif options.desugar_jdk11_legacy:
+      builders = [DESUGAR_JDK11_LEGACY_BOT]
+    else:
+      builders = [DESUGAR_JDK8_BOT]
     commit = git_utils.GetHeadRevision(utils.REPO_ROOT, use_main=True)
   if options.cl:
     trigger_cl(builders, options.cl)