Implement new startup API

Change-Id: I32b5999179c4e5cbb8e95dd91659b25e07eaef4d
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 172bfa2..24df37d 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -690,10 +690,7 @@
 
     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.setDumpInputFlags(getDumpInputFlags());
     internal.dumpOptions = dumpOptions();
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 6adecce..c9fd4f2 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -1067,10 +1067,7 @@
 
     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());
 
     if (!DETERMINISTIC_DEBUGGING) {
       assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
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/dex/StartupMixedSectionLayoutStrategy.java b/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
index 4d9a8ca..0833358 100644
--- a/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
+++ b/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
@@ -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/experimental/startup/EmptyStartupOrder.java b/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupOrder.java
index 8913368..fb1fc24 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
@@ -6,7 +6,6 @@
 
 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;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.PrunedItems;
@@ -24,7 +23,7 @@
   }
 
   @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 164475b..76020fc 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
@@ -7,11 +7,10 @@
 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;
@@ -28,24 +27,22 @@
 
 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) {
+  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.apply(
+          startupClass -> nonSyntheticStartupClasses.add(startupClass.getReference()),
+          startupMethod ->
+              nonSyntheticStartupClasses.add(startupMethod.getReference().getHolderType()),
+          syntheticStartupMethod ->
+              syntheticStartupClasses.add(syntheticStartupMethod.getSyntheticContextType()));
     }
   }
 
@@ -72,7 +69,7 @@
   }
 
   @Override
-  public Collection<StartupItem<DexType, DexMethod, ?>> getItems() {
+  public Collection<StartupItem> getItems() {
     return startupItems;
   }
 
@@ -83,27 +80,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);
   }
@@ -129,29 +127,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);
@@ -167,16 +163,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);
@@ -184,54 +177,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);
   }
@@ -248,8 +240,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/StartupCompleteness.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupCompleteness.java
index 43e306a..e6a0f85 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,12 +4,9 @@
 
 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.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;
@@ -81,26 +78,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/StartupOptions.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
index 86df003..01bfd2b 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.createFromFile(
+                        Paths.get(propertyValue), options.reporter)),
+            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 aa3d454..78a5970 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
@@ -7,7 +7,6 @@
 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.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.PrunedItems;
@@ -34,7 +33,7 @@
 
   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/StartupProfileProviderUtils.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java
new file mode 100644
index 0000000..2924e48
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.experimental.startup;
+
+import com.android.tools.r8.experimental.startup.profile.StartupProfileParser;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.startup.StartupProfileBuilder;
+import com.android.tools.r8.startup.StartupProfileProvider;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Path;
+
+public class StartupProfileProviderUtils {
+
+  public static StartupProfileProvider createFromFile(Path path, Reporter reporter) {
+    return new StartupProfileProvider() {
+
+      @Override
+      public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) {
+        try {
+          StartupProfileParser.create()
+              .parseLines(
+                  FileUtils.readAllLines(path).stream(),
+                  startupProfileBuilder,
+                  error ->
+                      reporter.warning(
+                          new StringDiagnostic(
+                              "Invalid descriptor for startup class or method: " + error)));
+        } 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/experimental/startup/profile/StartupClass.java b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupClass.java
index baab7f9..dbd8ef6 100644
--- 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
@@ -4,79 +4,104 @@
 
 package com.android.tools.r8.experimental.startup.profile;
 
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.startup.StartupClassBuilder;
+import com.android.tools.r8.utils.ClassReferenceUtils;
 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 class StartupClass extends StartupItem {
 
-  public StartupClass(int flags, C reference) {
-    super(flags, reference);
+  private final DexType type;
+
+  StartupClass(DexType type) {
+    this.type = type;
   }
 
-  public static <C, M> Builder<C, M> builder() {
-    return new Builder<>();
+  public static Builder builder() {
+    return new Builder();
   }
 
-  public static Builder<DexType, DexMethod> dexBuilder() {
-    return new Builder<>();
-  }
-
-  public static Builder<ClassReference, MethodReference> referenceBuilder() {
-    return new Builder<>();
+  public static Builder builder(DexItemFactory dexItemFactory) {
+    return new Builder(dexItemFactory);
   }
 
   @Override
   public void accept(
-      Consumer<StartupClass<C, M>> classConsumer, Consumer<StartupMethod<C, M>> methodConsumer) {
+      Consumer<StartupClass> classConsumer,
+      Consumer<StartupMethod> methodConsumer,
+      Consumer<SyntheticStartupMethod> syntheticMethodConsumer) {
     classConsumer.accept(this);
   }
 
   @Override
   public <T> T apply(
-      Function<StartupClass<C, M>, T> classFunction,
-      Function<StartupMethod<C, M>, T> methodFunction) {
+      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<C, M> asStartupClass() {
+  public StartupClass asStartupClass() {
     return this;
   }
 
   @Override
-  public void serializeToString(
-      StringBuilder builder,
-      Function<C, String> classSerializer,
-      Function<M, String> methodSerializer) {
-    if (isSynthetic()) {
-      builder.append('S');
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
     }
-    builder.append(classSerializer.apply(getReference()));
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    StartupClass that = (StartupClass) o;
+    return type == that.type;
   }
 
-  public static class Builder<C, M> extends StartupItem.Builder<C, M, Builder<C, M>> {
+  @Override
+  public int hashCode() {
+    return type.hashCode();
+  }
 
-    @Override
-    public Builder<C, M> setMethodReference(M reference) {
-      throw new Unreachable();
+  public static class Builder implements StartupClassBuilder {
+
+    private final DexItemFactory dexItemFactory;
+
+    private DexType type;
+
+    Builder() {
+      this(null);
+    }
+
+    Builder(DexItemFactory dexItemFactory) {
+      this.dexItemFactory = dexItemFactory;
     }
 
     @Override
-    public StartupClass<C, M> build() {
-      return new StartupClass<>(flags, classReference);
+    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
index 4590a3f..1854c07 100644
--- 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
@@ -4,35 +4,27 @@
 
 package com.android.tools.r8.experimental.startup.profile;
 
-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 class StartupItem {
 
   public abstract void accept(
-      Consumer<StartupClass<C, M>> classConsumer, Consumer<StartupMethod<C, M>> methodConsumer);
+      Consumer<StartupClass> classConsumer,
+      Consumer<StartupMethod> methodConsumer,
+      Consumer<SyntheticStartupMethod> syntheticMethodConsumer);
 
   public abstract <T> T apply(
-      Function<StartupClass<C, M>, T> classFunction,
-      Function<StartupMethod<C, M>, T> methodFunction);
+      Function<StartupClass, T> classFunction,
+      Function<StartupMethod, T> methodFunction,
+      Function<SyntheticStartupMethod, T> syntheticMethodFunction);
 
   public boolean isStartupClass() {
     return false;
   }
 
-  public StartupClass<C, M> asStartupClass() {
+  public StartupClass asStartupClass() {
+    assert false;
     return null;
   }
 
@@ -40,126 +32,17 @@
     return false;
   }
 
-  public StartupMethod<C, M> asStartupMethod() {
+  public StartupMethod asStartupMethod() {
+    assert false;
     return null;
   }
 
-  public static <C, M> Builder<C, M, ?> builder() {
-    return new Builder<>();
+  public boolean isSyntheticStartupMethod() {
+    return false;
   }
 
-  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;
-    }
+  public SyntheticStartupMethod asSyntheticStartupMethod() {
+    assert false;
+    return null;
   }
 }
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
index b6eb04b..9e45798 100644
--- 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
@@ -4,66 +4,104 @@
 
 package com.android.tools.r8.experimental.startup.profile;
 
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.references.ClassReference;
+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<C, M> extends StartupItem<C, M, M> {
+public class StartupMethod extends StartupItem {
 
-  public StartupMethod(int flags, M reference) {
-    super(flags, reference);
+  private final DexMethod method;
+
+  StartupMethod(DexMethod method) {
+    this.method = method;
   }
 
-  public static Builder<ClassReference, MethodReference> referenceBuilder() {
-    return new Builder<>();
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static Builder builder(DexItemFactory dexItemFactory) {
+    return new Builder(dexItemFactory);
   }
 
   @Override
   public void accept(
-      Consumer<StartupClass<C, M>> classConsumer, Consumer<StartupMethod<C, M>> methodConsumer) {
+      Consumer<StartupClass> classConsumer,
+      Consumer<StartupMethod> methodConsumer,
+      Consumer<SyntheticStartupMethod> syntheticMethodConsumer) {
     methodConsumer.accept(this);
   }
 
   @Override
   public <T> T apply(
-      Function<StartupClass<C, M>, T> classFunction,
-      Function<StartupMethod<C, M>, T> methodFunction) {
+      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<C, M> asStartupMethod() {
+  public StartupMethod asStartupMethod() {
     return this;
   }
 
   @Override
-  public void serializeToString(
-      StringBuilder builder,
-      Function<C, String> classSerializer,
-      Function<M, String> methodSerializer) {
-    if (isSynthetic()) {
-      builder.append('S');
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
     }
-    builder.append(methodSerializer.apply(getReference()));
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    StartupMethod that = (StartupMethod) o;
+    return method == that.method;
   }
 
-  public static class Builder<C, M> extends StartupItem.Builder<C, M, Builder<C, M>> {
+  @Override
+  public int hashCode() {
+    return method.hashCode();
+  }
 
-    @Override
-    public Builder<C, M> setClassReference(C reference) {
-      throw new Unreachable();
+  public static class Builder implements StartupMethodBuilder {
+
+    private final DexItemFactory dexItemFactory;
+
+    private DexMethod method;
+
+    Builder() {
+      this(null);
+    }
+
+    Builder(DexItemFactory dexItemFactory) {
+      this.dexItemFactory = dexItemFactory;
     }
 
     @Override
-    public StartupMethod<C, M> build() {
-      return new StartupMethod<>(flags, methodReference);
+    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
index c2e65b7..a0d6204 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfile.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfile.java
@@ -4,22 +4,23 @@
 
 package com.android.tools.r8.experimental.startup.profile;
 
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexItemFactory;
+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.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.Collection;
 import java.util.List;
 import java.util.function.Consumer;
 
 public class StartupProfile {
 
-  private final List<StartupItem<DexType, DexMethod, ?>> startupItems;
+  private final List<StartupItem> startupItems;
 
-  public StartupProfile(List<StartupItem<DexType, DexMethod, ?>> startupItems) {
+  public StartupProfile(List<StartupItem> startupItems) {
     this.startupItems = startupItems;
   }
 
@@ -27,6 +28,10 @@
     return new Builder();
   }
 
+  public static Builder builder(DexItemFactory dexItemFactory) {
+    return new Builder(dexItemFactory);
+  }
+
   /**
    * Parses the supplied startup configuration, if any. The startup configuration is a list of class
    * and method descriptors.
@@ -43,60 +48,63 @@
    * </pre>
    */
   public static StartupProfile parseStartupProfile(InternalOptions options) {
-    if (!options.getStartupOptions().hasStartupProfileProvider()) {
+    if (!options.getStartupOptions().hasStartupProfileProviders()) {
       return null;
     }
-    StartupProfileProvider resource = options.getStartupOptions().getStartupProfileProvider();
-    List<String> startupDescriptors = StringUtils.splitLines(resource.get());
-    return createStartupConfigurationFromLines(options, startupDescriptors);
+    Collection<StartupProfileProvider> startupProfileProviders =
+        options.getStartupOptions().getStartupProfileProviders();
+    StartupProfile.Builder startupProfileBuilder = StartupProfile.builder(options.dexItemFactory());
+    for (StartupProfileProvider startupProfileProvider : startupProfileProviders) {
+      startupProfileProvider.getStartupProfile(startupProfileBuilder);
+    }
+    return startupProfileBuilder.build();
   }
 
-  public static StartupProfile createStartupConfigurationFromLines(
-      InternalOptions options, List<String> startupDescriptors) {
-    List<StartupItem<DexType, DexMethod, ?>> startupItems = new ArrayList<>();
-    StartupProfileParser.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() {
+  public List<StartupItem> 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');
+  public static class Builder implements StartupProfileBuilder {
+
+    private final DexItemFactory dexItemFactory;
+    private final ImmutableList.Builder<StartupItem> startupItemsBuilder = ImmutableList.builder();
+
+    Builder() {
+      this(null);
     }
-    return builder.toString();
-  }
 
-  public static class Builder {
+    Builder(DexItemFactory dexItemFactory) {
+      this.dexItemFactory = dexItemFactory;
+    }
 
-    private final ImmutableList.Builder<StartupItem<DexType, DexMethod, ?>> startupItemsBuilder =
-        ImmutableList.builder();
+    @Override
+    public Builder addStartupClass(Consumer<StartupClassBuilder> startupClassBuilderConsumer) {
+      StartupClass.Builder startupClassBuilder = StartupClass.builder(dexItemFactory);
+      startupClassBuilderConsumer.accept(startupClassBuilder);
+      return addStartupItem(startupClassBuilder.build());
+    }
 
-    public Builder addStartupItem(StartupItem<DexType, DexMethod, ?> startupItem) {
+    @Override
+    public Builder addStartupMethod(Consumer<StartupMethodBuilder> startupMethodBuilderConsumer) {
+      StartupMethod.Builder startupMethodBuilder = StartupMethod.builder(dexItemFactory);
+      startupMethodBuilderConsumer.accept(startupMethodBuilder);
+      return addStartupItem(startupMethodBuilder.build());
+    }
+
+    @Override
+    public StartupProfileBuilder addSyntheticStartupMethod(
+        Consumer<SyntheticStartupMethodBuilder> syntheticStartupMethodBuilderConsumer) {
+      SyntheticStartupMethod.Builder syntheticStartupMethodBuilder =
+          SyntheticStartupMethod.builder(dexItemFactory);
+      syntheticStartupMethodBuilderConsumer.accept(syntheticStartupMethodBuilder);
+      return addStartupItem(syntheticStartupMethodBuilder.build());
+    }
+
+    private Builder addStartupItem(StartupItem 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;
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileParser.java b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileParser.java
index a386c31..cc205bf 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileParser.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileParser.java
@@ -4,90 +4,49 @@
 
 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.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.startup.StartupProfileBuilder;
+import com.android.tools.r8.utils.BooleanBox;
 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;
+import java.util.stream.Stream;
 
-public class StartupProfileParser<C, M, T> {
+public class StartupProfileParser {
 
-  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;
-
-  StartupProfileParser(
-      Function<String, C> classFactory,
-      MethodFactory<C, M, T> methodFactory,
-      Function<String, T> typeFactory) {
-    this.classFactory = classFactory;
-    this.methodFactory = methodFactory;
-    this.typeFactory = typeFactory;
-  }
-
-  public static StartupProfileParser<DexType, DexMethod, DexType> createDexParser(
-      DexItemFactory dexItemFactory) {
-    return new StartupProfileParser<>(
-        dexItemFactory::createType,
-        (methodHolder, methodName, methodParameters, methodReturnType) ->
-            dexItemFactory.createMethod(
-                methodHolder,
-                dexItemFactory.createProto(methodReturnType, methodParameters),
-                dexItemFactory.createString(methodName)),
-        dexItemFactory::createType);
-  }
-
-  public static StartupProfileParser<ClassReference, MethodReference, TypeReference>
-      createReferenceParser() {
-    return new StartupProfileParser<>(
-        Reference::classFromDescriptor, Reference::method, Reference::returnTypeFromDescriptor);
+  public static StartupProfileParser create() {
+    return new StartupProfileParser();
   }
 
   public void parseLines(
-      List<String> startupDescriptors,
-      Consumer<? super StartupClass<C, M>> startupClassConsumer,
-      Consumer<? super StartupMethod<C, M>> startupMethodConsumer,
+      Stream<String> startupDescriptors,
+      StartupProfileBuilder startupProfileBuilder,
       Consumer<String> parseErrorHandler) {
-    for (String startupDescriptor : startupDescriptors) {
-      if (!startupDescriptor.isEmpty()) {
-        parseLine(
-            startupDescriptor, startupClassConsumer, startupMethodConsumer, parseErrorHandler);
-      }
-    }
+    startupDescriptors.forEach(
+        startupDescriptor -> {
+          if (!startupDescriptor.isEmpty()) {
+            parseLine(startupDescriptor, startupProfileBuilder, parseErrorHandler);
+          }
+        });
   }
 
   public void parseLine(
       String startupDescriptor,
-      Consumer<? super StartupClass<C, M>> startupClassConsumer,
-      Consumer<? super StartupMethod<C, M>> startupMethodConsumer,
+      StartupProfileBuilder startupProfileBuilder,
       Consumer<String> parseErrorHandler) {
-    StartupItem.Builder<C, M, ?> startupItemBuilder = StartupItem.builder();
-    startupDescriptor = parseSyntheticFlag(startupDescriptor, startupItemBuilder);
+    BooleanBox syntheticFlag = new BooleanBox();
+    startupDescriptor = parseSyntheticFlag(startupDescriptor, syntheticFlag);
     parseStartupClassOrMethod(
-        startupDescriptor,
-        startupItemBuilder,
-        startupClassConsumer,
-        startupMethodConsumer,
-        parseErrorHandler);
+        startupDescriptor, startupProfileBuilder, syntheticFlag, parseErrorHandler);
   }
 
-  private static String parseSyntheticFlag(
-      String startupDescriptor, StartupItem.Builder<?, ?, ?> startupItemBuilder) {
+  private static String parseSyntheticFlag(String startupDescriptor, BooleanBox syntheticFlag) {
     if (!startupDescriptor.isEmpty() && startupDescriptor.charAt(0) == 'S') {
-      startupItemBuilder.setSynthetic();
+      syntheticFlag.set();
       return startupDescriptor.substring(1);
     }
     return startupDescriptor;
@@ -95,24 +54,34 @@
 
   private void parseStartupClassOrMethod(
       String startupDescriptor,
-      StartupItem.Builder<C, M, ?> startupItemBuilder,
-      Consumer<? super StartupClass<C, M>> startupClassConsumer,
-      Consumer<? super StartupMethod<C, M>> startupMethodConsumer,
+      StartupProfileBuilder startupProfileBuilder,
+      BooleanBox syntheticFlag,
       Consumer<String> parseErrorHandler) {
     int arrowStartIndex = getArrowStartIndex(startupDescriptor);
     if (arrowStartIndex >= 0) {
-      M startupMethod = parseStartupMethodDescriptor(startupDescriptor, arrowStartIndex);
-      if (startupMethod != null) {
-        startupMethodConsumer.accept(
-            startupItemBuilder.setMethodReference(startupMethod).buildStartupMethod());
+      if (syntheticFlag.isFalse()) {
+        MethodReference startupMethod =
+            parseStartupMethodDescriptor(startupDescriptor, arrowStartIndex);
+        if (startupMethod != null) {
+          startupProfileBuilder.addStartupMethod(
+              startupMethodBuilder -> startupMethodBuilder.setMethodReference(startupMethod));
+        } else {
+          parseErrorHandler.accept(startupDescriptor);
+        }
       } else {
         parseErrorHandler.accept(startupDescriptor);
       }
     } else {
-      C startupClass = parseStartupClassDescriptor(startupDescriptor);
+      ClassReference startupClass = parseStartupClassDescriptor(startupDescriptor);
       if (startupClass != null) {
-        startupClassConsumer.accept(
-            startupItemBuilder.setClassReference(startupClass).buildStartupClass());
+        if (syntheticFlag.isFalse()) {
+          startupProfileBuilder.addStartupClass(
+              startupClassBuilder -> startupClassBuilder.setClassReference(startupClass));
+        } else {
+          startupProfileBuilder.addSyntheticStartupMethod(
+              syntheticStartupMethodBuilder ->
+                  syntheticStartupMethodBuilder.setSyntheticContextReference(startupClass));
+        }
       } else {
         parseErrorHandler.accept(startupDescriptor);
       }
@@ -123,17 +92,18 @@
     return startupDescriptor.indexOf("->");
   }
 
-  private C parseStartupClassDescriptor(String startupClassDescriptor) {
+  private ClassReference parseStartupClassDescriptor(String startupClassDescriptor) {
     if (DescriptorUtils.isClassDescriptor(startupClassDescriptor)) {
-      return classFactory.apply(startupClassDescriptor);
+      return Reference.classFromDescriptor(startupClassDescriptor);
     } else {
       return null;
     }
   }
 
-  private M parseStartupMethodDescriptor(String startupMethodDescriptor, int arrowStartIndex) {
+  private MethodReference parseStartupMethodDescriptor(
+      String startupMethodDescriptor, int arrowStartIndex) {
     String classDescriptor = startupMethodDescriptor.substring(0, arrowStartIndex);
-    C methodHolder = parseStartupClassDescriptor(classDescriptor);
+    ClassReference methodHolder = parseStartupClassDescriptor(classDescriptor);
     if (methodHolder == null) {
       return null;
     }
@@ -150,14 +120,15 @@
     return parseStartupMethodProto(methodHolder, methodName, protoDescriptor);
   }
 
-  private M parseStartupMethodProto(C methodHolder, String methodName, String protoDescriptor) {
-    List<T> parameterTypes = new ArrayList<>();
+  private MethodReference parseStartupMethodProto(
+      ClassReference methodHolder, String methodName, String protoDescriptor) {
+    List<TypeReference> parameterTypes = new ArrayList<>();
     for (String parameterTypeDescriptor :
         DescriptorUtils.getArgumentTypeDescriptors(protoDescriptor)) {
-      parameterTypes.add(typeFactory.apply(parameterTypeDescriptor));
+      parameterTypes.add(Reference.typeFromDescriptor(parameterTypeDescriptor));
     }
     String returnTypeDescriptor = DescriptorUtils.getReturnTypeDescriptor(protoDescriptor);
-    T returnType = typeFactory.apply(returnTypeDescriptor);
-    return methodFactory.createMethod(methodHolder, methodName, parameterTypes, returnType);
+    TypeReference returnType = Reference.returnTypeFromDescriptor(returnTypeDescriptor);
+    return Reference.method(methodHolder, methodName, parameterTypes, returnType);
   }
 }
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..eedcadd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/startup/profile/SyntheticStartupMethod.java
@@ -0,0 +1,108 @@
+// 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();
+  }
+
+  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/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/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index f02405c..78c17ec 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -816,7 +816,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();
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 9ab1f03..f9b2f74 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.profile.StartupClass;
-import com.android.tools.r8.experimental.startup.profile.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;
@@ -52,30 +50,18 @@
         .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();
+                      for (Class<?> startupClass : getStartupClasses()) {
+                        ClassReference startupClassReference =
+                            Reference.classFromClass(startupClass);
+                        startupProfileBuilder.addStartupClass(
+                            startupClassBuilder ->
+                                startupClassBuilder.setClassReference(startupClassReference));
+                      }
                     }
 
                     @Override
@@ -83,7 +69,9 @@
                       return Origin.unknown();
                     }
                   };
-              options.getStartupOptions().setStartupProfileProvider(startupProfileProvider);
+              options
+                  .getStartupOptions()
+                  .setStartupProfileProviders(Collections.singleton(startupProfileProvider));
             })
         .addHorizontallyMergedClassesInspector(
             inspector ->
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..b6572fc 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
@@ -82,9 +82,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,11 +95,6 @@
 
     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) {
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..35ef96e 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1719Test.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1719Test.java
@@ -38,6 +38,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Collections;
 import java.util.concurrent.ExecutionException;
 import org.junit.After;
 import org.junit.Test;
@@ -149,7 +150,8 @@
                       if (startupProfileProvider != null) {
                         options
                             .getStartupOptions()
-                            .setStartupProfileProvider(startupProfileProvider)
+                            .setStartupProfileProviders(
+                                Collections.singleton(startupProfileProvider))
                             .setEnableMinimalStartupDex(enableMinimalStartupDex)
                             .setEnableStartupBoundaryOptimizations(
                                 enableStartupBoundaryOptimizations);
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..0af6a45 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,23 +13,22 @@
 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.experimental.startup.StartupProfileProviderUtils;
 import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.ZipUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Collections;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -238,25 +237,10 @@
       boolean enableStartupBoundaryOptimizations,
       Path outDirectory)
       throws Exception {
+    Reporter reporter = new Reporter();
     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();
-          }
-        };
-
+        StartupProfileProviderUtils.createFromFile(
+            chromeDirectory.resolve("startup.txt"), reporter);
     buildR8(
         testBuilder ->
             testBuilder.addOptionsModification(
@@ -265,7 +249,7 @@
                         .getStartupOptions()
                         .setEnableMinimalStartupDex(enableMinimalStartupDex)
                         .setEnableStartupBoundaryOptimizations(enableStartupBoundaryOptimizations)
-                        .setStartupProfileProvider(startupProfileProvider)),
+                        .setStartupProfileProviders(Collections.singleton(startupProfileProvider))),
         outDirectory);
   }
 
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 b26ff87..b171421 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.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.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())
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 eda0c51..d6d12a8 100644
--- a/src/test/java/com/android/tools/r8/startup/MinimalStartupDexTest.java
+++ b/src/test/java/com/android/tools/r8/startup/MinimalStartupDexTest.java
@@ -12,9 +12,7 @@
 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.profile.StartupItem;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.MethodReference;
+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;
@@ -45,7 +43,7 @@
 
   @Test
   public void test() throws Exception {
-    List<StartupItem<ClassReference, MethodReference, ?>> startupList = new ArrayList<>();
+    List<ExternalStartupItem> startupList = new ArrayList<>();
     testForD8(parameters.getBackend())
         .addInnerClasses(getClass())
         .apply(
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 13257e1..faa893b 100644
--- a/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java
+++ b/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java
@@ -9,12 +9,10 @@
 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.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.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.BooleanUtils;
 import com.android.tools.r8.utils.MethodReferenceUtils;
@@ -48,7 +46,7 @@
   @Test
   public void test() throws Exception {
     Path out = temp.newFolder().toPath().resolve("out.txt").toAbsolutePath();
-    List<StartupItem<ClassReference, MethodReference, ?>> startupList = new ArrayList<>();
+    List<ExternalStartupItem> startupList = new ArrayList<>();
     testForD8(parameters.getBackend())
         .addInnerClasses(getClass())
         .applyIf(
@@ -75,19 +73,18 @@
     return ImmutableList.of("foo");
   }
 
-  private List<StartupItem<ClassReference, MethodReference, ?>> getExpectedStartupList()
-      throws NoSuchMethodException {
+  private List<ExternalStartupItem> getExpectedStartupList() throws NoSuchMethodException {
     return ImmutableList.of(
-        StartupClass.referenceBuilder()
+        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 64c4c56..e9c4224 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,14 @@
 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.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.DexProgramClass;
 import com.android.tools.r8.ir.desugar.LambdaClass;
 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;
@@ -90,7 +90,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<>();
+    List<ExternalStartupItem> startupList = new ArrayList<>();
     testForD8(parameters.getBackend())
         .addProgramFiles(optimizedApp)
         .apply(
@@ -125,7 +125,7 @@
   @Test
   public void testLayoutUsingR8() throws Exception {
     // First generate a startup list for the original app.
-    List<StartupItem<ClassReference, MethodReference, ?>> startupList = new ArrayList<>();
+    List<ExternalStartupItem> startupList = new ArrayList<>();
     D8TestCompileResult instrumentationCompileResult =
         testForD8(parameters.getBackend())
             .addInnerClasses(getClass())
@@ -167,7 +167,7 @@
   private void configureStartupOptions(
       TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder,
       CodeInspector inspector,
-      List<StartupItem<ClassReference, MethodReference, ?>> startupList) {
+      List<ExternalStartupItem> startupList) {
     testBuilder
         .addOptionsModification(
             options -> {
@@ -188,36 +188,30 @@
   }
 
   @SuppressWarnings("unchecked")
-  private List<StartupItem<ClassReference, MethodReference, ?>> getExpectedStartupList(
+  private List<ExternalStartupItem> getExpectedStartupList(
       CodeInspector inspector, boolean isStartupListForOriginalApp) throws NoSuchMethodException {
-    ImmutableList.Builder<StartupItem<ClassReference, MethodReference, ?>> builder =
-        ImmutableList.builder();
+    ImmutableList.Builder<ExternalStartupItem> builder = ImmutableList.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 +232,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 +247,7 @@
                         ImmutableList.of(Reference.classFromClass(Object.class)),
                         null))
                 .build(),
-            StartupMethod.referenceBuilder()
+            ExternalStartupMethod.builder()
                 .setMethodReference(
                     Reference.method(
                         Reference.classFromClass(B.class),
@@ -263,16 +257,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 5b30a0a..a824cc4 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,13 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-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.DexProgramClass;
 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;
@@ -61,7 +61,7 @@
 
   @Test
   public void test() throws Exception {
-    List<StartupItem<ClassReference, MethodReference, ?>> startupList = new ArrayList<>();
+    List<ExternalStartupItem> startupList = new ArrayList<>();
     testForD8(parameters.getBackend())
         .addInnerClasses(getClass())
         .apply(
@@ -101,38 +101,30 @@
     return ImmutableList.of("A", "B", "C");
   }
 
-  private List<StartupItem<ClassReference, MethodReference, ?>> getExpectedStartupList()
-      throws NoSuchMethodException {
+  private List<ExternalStartupItem> getExpectedStartupList() throws NoSuchMethodException {
     return ImmutableList.of(
-        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")))
             .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/profile/ExternalStartupClass.java b/src/test/java/com/android/tools/r8/startup/profile/ExternalStartupClass.java
new file mode 100644
index 0000000..c6caccf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/profile/ExternalStartupClass.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.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();
+  }
+
+  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..0d73a7a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/profile/ExternalStartupMethod.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.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 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();
+  }
+
+  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..628cdec
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/profile/ExternalSyntheticStartupMethod.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.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();
+  }
+
+  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 2011ff1..a9ca8c5 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
@@ -14,28 +14,25 @@
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ThrowableConsumer;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.experimental.startup.instrumentation.StartupInstrumentationOptions;
-import com.android.tools.r8.experimental.startup.profile.StartupItem;
-import com.android.tools.r8.experimental.startup.profile.StartupProfile;
 import com.android.tools.r8.experimental.startup.profile.StartupProfileParser;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.MethodReference;
-import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.startup.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.StringUtils;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.Collections;
 import java.util.List;
 import java.util.function.Consumer;
 import org.junit.rules.TemporaryFolder;
@@ -53,6 +50,39 @@
     }
   }
 
+  private static StartupProfileBuilder createStartupItemFactory(
+      Consumer<ExternalStartupItem> startupItemConsumer) {
+    return new StartupProfileBuilder() {
+      @Override
+      public StartupProfileBuilder addStartupClass(
+          Consumer<StartupClassBuilder> startupClassBuilderConsumer) {
+        ExternalStartupClass.Builder startupClassBuilder = ExternalStartupClass.builder();
+        startupClassBuilderConsumer.accept(startupClassBuilder);
+        startupItemConsumer.accept(startupClassBuilder.build());
+        return this;
+      }
+
+      @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;
+      }
+    };
+  }
+
   public static ThrowableConsumer<D8TestBuilder>
       enableStartupInstrumentationForOriginalAppUsingFile(TestParameters parameters) {
     return testBuilder ->
@@ -66,12 +96,6 @@
   }
 
   public static ThrowableConsumer<D8TestBuilder>
-      enableStartupInstrumentationForOptimizedAppUsingFile(TestParameters parameters) {
-    return testBuilder ->
-        enableStartupInstrumentation(testBuilder, parameters, AppVariant.OPTIMIZED, false);
-  }
-
-  public static ThrowableConsumer<D8TestBuilder>
       enableStartupInstrumentationForOptimizedAppUsingLogcat(TestParameters parameters) {
     return testBuilder ->
         enableStartupInstrumentation(testBuilder, parameters, AppVariant.OPTIMIZED, true);
@@ -108,26 +132,22 @@
   }
 
   public static void readStartupListFromFile(
-      Path path, Consumer<StartupItem<ClassReference, MethodReference, ?>> startupItemConsumer)
-      throws IOException {
-    StartupProfileParser.createReferenceParser()
+      Path path, Consumer<ExternalStartupItem> startupItemConsumer) throws IOException {
+    StartupProfileParser.create()
         .parseLines(
-            Files.readAllLines(path),
-            startupItemConsumer,
-            startupItemConsumer,
+            Files.readAllLines(path).stream(),
+            createStartupItemFactory(startupItemConsumer),
             error -> fail("Unexpected parse error: " + error));
   }
 
   public static ThrowingConsumer<D8TestRunResult, RuntimeException> removeStartupListFromStdout(
-      Consumer<StartupItem<ClassReference, MethodReference, ?>> startupItemConsumer) {
+      Consumer<ExternalStartupItem> startupItemConsumer) {
     return runResult -> removeStartupListFromStdout(runResult, startupItemConsumer);
   }
 
   public static void removeStartupListFromStdout(
-      D8TestRunResult runResult,
-      Consumer<StartupItem<ClassReference, MethodReference, ?>> startupItemConsumer) {
-    StartupProfileParser<ClassReference, MethodReference, TypeReference> parser =
-        StartupProfileParser.createReferenceParser();
+      D8TestRunResult runResult, Consumer<ExternalStartupItem> startupItemConsumer) {
+    StartupProfileParser parser = StartupProfileParser.create();
     StringBuilder stdoutBuilder = new StringBuilder();
     String startupDescriptorPrefix = "[" + startupInstrumentationTag + "] ";
     for (String line : StringUtils.splitLines(runResult.getStdOut(), true)) {
@@ -135,8 +155,7 @@
         String message = line.substring(startupDescriptorPrefix.length());
         parser.parseLine(
             message,
-            startupItemConsumer,
-            startupItemConsumer,
+            createStartupItemFactory(startupItemConsumer),
             error -> fail("Unexpected parse error: " + error));
       } else {
         stdoutBuilder.append(line).append(System.lineSeparator());
@@ -146,30 +165,31 @@
   }
 
   public static void setStartupConfiguration(
-      TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder,
-      List<StartupItem<ClassReference, MethodReference, ?>> startupItems) {
+      TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder, List<ExternalStartupItem> 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();
-                }
-
-                @Override
                 public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) {
-                  throw new Unimplemented();
+                  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
@@ -177,27 +197,12 @@
                   return Origin.unknown();
                 }
               };
-          options.getStartupOptions().setStartupProfileProvider(startupProfileProvider);
+          options
+              .getStartupOptions()
+              .setStartupProfileProviders(Collections.singleton(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();
-  }
-
   private static byte[] getTransformedAndroidUtilLog() throws IOException {
     return transformer(Log.class).setClassDescriptor("Landroid/util/Log;").transform();
   }
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..4753c76 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
+46ac3bedbdba8732e48d13fcc94967f43ae1d91b
\ No newline at end of file