Trace executed methods during startup

Fixes: b/238111450
Change-Id: Ifb427528d1a81dac1f718d347759a4b9152bc6a7
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 eb03451..c71d0d3 100644
--- a/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
+++ b/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
@@ -79,7 +79,7 @@
             virtualFile.classes().size());
     LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(appView, true);
     StartupIndexedItemCollection indexedItemCollection = new StartupIndexedItemCollection();
-    for (StartupClass<DexType> startupClass : startupOrderForWriting.getClasses()) {
+    for (StartupClass<DexType, DexMethod> startupClass : startupOrderForWriting.getClasses()) {
       assert !startupClass.isSynthetic();
       DexProgramClass definition = virtualFileDefinitions.get(startupClass.getReference());
       if (definition != null) {
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 3566ad1..7bbaba0 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
@@ -5,6 +5,7 @@
 package com.android.tools.r8.experimental.startup;
 
 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;
@@ -22,7 +23,7 @@
   }
 
   @Override
-  public Collection<StartupClass<DexType>> getClasses() {
+  public Collection<StartupClass<DexType, DexMethod>> getClasses() {
     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 673e0de..4122aa0 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
@@ -6,6 +6,7 @@
 
 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.DexType;
 import com.android.tools.r8.graph.GraphLens;
@@ -25,16 +26,16 @@
 
 public class NonEmptyStartupOrder extends StartupOrder {
 
-  private final LinkedHashSet<StartupClass<DexType>> startupClasses;
+  private final LinkedHashSet<StartupClass<DexType, DexMethod>> startupClasses;
 
   // Redundant sets to allow efficient querying without boxing.
   private final Set<DexType> nonSyntheticStartupClasses = Sets.newIdentityHashSet();
   private final Set<DexType> syntheticStartupClasses = Sets.newIdentityHashSet();
 
-  NonEmptyStartupOrder(LinkedHashSet<StartupClass<DexType>> startupClasses) {
+  NonEmptyStartupOrder(LinkedHashSet<StartupClass<DexType, DexMethod>> startupClasses) {
     assert !startupClasses.isEmpty();
     this.startupClasses = startupClasses;
-    for (StartupClass<DexType> startupClass : startupClasses) {
+    for (StartupClass<DexType, DexMethod> startupClass : startupClasses) {
       if (startupClass.isSynthetic()) {
         syntheticStartupClasses.add(startupClass.getReference());
       } else {
@@ -66,7 +67,7 @@
   }
 
   @Override
-  public Collection<StartupClass<DexType>> getClasses() {
+  public Collection<StartupClass<DexType, DexMethod>> getClasses() {
     return startupClasses;
   }
 
@@ -77,13 +78,13 @@
 
   @Override
   public StartupOrder rewrittenWithLens(GraphLens graphLens) {
-    LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses =
+    LinkedHashSet<StartupClass<DexType, DexMethod>> rewrittenStartupClasses =
         new LinkedHashSet<>(startupClasses.size());
-    for (StartupClass<DexType> startupClass : startupClasses) {
+    for (StartupClass<DexType, DexMethod> startupClass : startupClasses) {
       rewrittenStartupClasses.add(
-          StartupClass.<DexType>builder()
+          StartupClass.dexBuilder()
               .setFlags(startupClass.getFlags())
-              .setReference(graphLens.lookupType(startupClass.getReference()))
+              .setClassReference(graphLens.lookupType(startupClass.getReference()))
               .build());
     }
     return createNonEmpty(rewrittenStartupClasses);
@@ -91,7 +92,7 @@
 
   @Override
   public StartupOrder toStartupOrderForWriting(AppView<?> appView) {
-    LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses =
+    LinkedHashSet<StartupClass<DexType, DexMethod>> rewrittenStartupClasses =
         new LinkedHashSet<>(startupClasses.size());
     Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses =
         new IdentityHashMap<>();
@@ -105,7 +106,7 @@
         }
       }
     }
-    for (StartupClass<DexType> startupClass : startupClasses) {
+    for (StartupClass<DexType, DexMethod> startupClass : startupClasses) {
       addStartupClass(
           startupClass, rewrittenStartupClasses, syntheticContextsToSyntheticClasses, appView);
     }
@@ -114,8 +115,8 @@
   }
 
   private static void addStartupClass(
-      StartupClass<DexType> startupClass,
-      LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses,
+      StartupClass<DexType, DexMethod> startupClass,
+      LinkedHashSet<StartupClass<DexType, DexMethod>> rewrittenStartupClasses,
       Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses,
       AppView<?> appView) {
     if (startupClass.isSynthetic()) {
@@ -131,14 +132,15 @@
   }
 
   private static boolean addClass(
-      DexProgramClass clazz, LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses) {
+      DexProgramClass clazz,
+      LinkedHashSet<StartupClass<DexType, DexMethod>> rewrittenStartupClasses) {
     return rewrittenStartupClasses.add(
-        StartupClass.<DexType>builder().setReference(clazz.getType()).build());
+        StartupClass.dexBuilder().setClassReference(clazz.getType()).build());
   }
 
   private static void addClassAndParentClasses(
       DexType type,
-      LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses,
+      LinkedHashSet<StartupClass<DexType, DexMethod>> rewrittenStartupClasses,
       AppView<?> appView) {
     DexProgramClass definition = appView.app().programDefinitionFor(type);
     if (definition != null) {
@@ -148,7 +150,7 @@
 
   private static void addClassAndParentClasses(
       DexProgramClass clazz,
-      LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses,
+      LinkedHashSet<StartupClass<DexType, DexMethod>> rewrittenStartupClasses,
       AppView<?> appView) {
     if (addClass(clazz, rewrittenStartupClasses)) {
       addParentClasses(clazz, rewrittenStartupClasses, appView);
@@ -157,7 +159,7 @@
 
   private static void addParentClasses(
       DexProgramClass clazz,
-      LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses,
+      LinkedHashSet<StartupClass<DexType, DexMethod>> rewrittenStartupClasses,
       AppView<?> appView) {
     clazz.forEachImmediateSupertype(
         supertype -> addClassAndParentClasses(supertype, rewrittenStartupClasses, appView));
@@ -165,12 +167,12 @@
 
   @Override
   public StartupOrder withoutPrunedItems(PrunedItems prunedItems, SyntheticItems syntheticItems) {
-    LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses =
+    LinkedHashSet<StartupClass<DexType, DexMethod>> rewrittenStartupClasses =
         new LinkedHashSet<>(startupClasses.size());
     LazyBox<Set<DexType>> contextsOfLiveSynthetics =
         new LazyBox<>(
             () -> computeContextsOfLiveSynthetics(prunedItems.getPrunedApp(), syntheticItems));
-    for (StartupClass<DexType> startupClass : startupClasses) {
+    for (StartupClass<DexType, DexMethod> startupClass : startupClasses) {
       // Only prune non-synthetic classes, since the pruning of a class does not imply that all
       // classes synthesized from it have been pruned.
       if (startupClass.isSynthetic()) {
@@ -196,7 +198,8 @@
     return contextsOfLiveSynthetics;
   }
 
-  private StartupOrder createNonEmpty(LinkedHashSet<StartupClass<DexType>> startupClasses) {
+  private StartupOrder createNonEmpty(
+      LinkedHashSet<StartupClass<DexType, DexMethod>> startupClasses) {
     if (startupClasses.isEmpty()) {
       assert false;
       return empty();
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupClass.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupClass.java
index 9338d5f..0f76036 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupClass.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupClass.java
@@ -4,84 +4,47 @@
 
 package com.android.tools.r8.experimental.startup;
 
-public class StartupClass<T> {
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
 
-  private static final int FLAG_SYNTHETIC = 1;
+// 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> {
 
-  private final int flags;
-  private final T reference;
-
-  public StartupClass(int flags, T reference) {
-    this.flags = flags;
-    this.reference = reference;
+  public StartupClass(int flags, C reference) {
+    super(flags, reference);
   }
 
-  public static <T> Builder<T> builder() {
+  public static <C, M> Builder<C, M> builder() {
     return new Builder<>();
   }
 
-  public int getFlags() {
-    return flags;
-  }
-
-  public T getReference() {
-    return reference;
-  }
-
-  public boolean isSynthetic() {
-    return (flags & FLAG_SYNTHETIC) != 0;
+  public static Builder<DexType, DexMethod> dexBuilder() {
+    return new Builder<>();
   }
 
   @Override
-  public boolean equals(Object obj) {
-    if (this == obj) {
-      return true;
-    }
-    if (obj == null || getClass() != obj.getClass()) {
-      return false;
-    }
-    StartupClass<?> startupClass = (StartupClass<?>) obj;
-    return flags == startupClass.flags && reference.equals(startupClass.reference);
+  public boolean isStartupClass() {
+    return true;
   }
 
   @Override
-  public int hashCode() {
-    assert flags <= 1;
-    return (reference.hashCode() << 1) | flags;
+  public StartupClass<C, M> asStartupClass() {
+    return this;
   }
 
-  @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> extends StartupItem.Builder<C, M, Builder<C, M>> {
 
-  public static class Builder<T> {
-
-    private int flags;
-    private T reference;
-
-    public Builder<T> setFlags(int flags) {
-      this.flags = flags;
-      return this;
+    @Override
+    public Builder<C, M> setMethodReference(M reference) {
+      throw new Unreachable();
     }
 
-    public Builder<T> setReference(T reference) {
-      this.reference = reference;
-      return this;
-    }
-
-    public Builder<T> setSynthetic() {
-      this.flags |= FLAG_SYNTHETIC;
-      return this;
-    }
-
-    public StartupClass<T> build() {
-      return new StartupClass<>(flags, reference);
+    @Override
+    public StartupClass<C, M> build() {
+      return new StartupClass<>(flags, classReference);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java
index 9f16dce..d6da209 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java
@@ -23,13 +23,10 @@
 
 public class StartupConfiguration {
 
-  private final List<StartupClass<DexType>> startupClasses;
-  private final List<DexMethod> startupMethods;
+  private final List<StartupClass<DexType, DexMethod>> startupClasses;
 
-  public StartupConfiguration(
-      List<StartupClass<DexType>> startupClasses, List<DexMethod> startupMethods) {
+  public StartupConfiguration(List<StartupClass<DexType, DexMethod>> startupClasses) {
     this.startupClasses = startupClasses;
-    this.startupMethods = startupMethods;
   }
 
   public static Builder builder() {
@@ -79,51 +76,66 @@
 
   public static StartupConfiguration createStartupConfigurationFromLines(
       DexItemFactory dexItemFactory, Reporter reporter, List<String> startupDescriptors) {
-    List<StartupClass<DexType>> startupClasses = new ArrayList<>();
-    List<DexMethod> startupMethods = new ArrayList<>();
+    List<StartupClass<DexType, DexMethod>> startupClasses = new ArrayList<>();
     for (String startupDescriptor : startupDescriptors) {
       if (startupDescriptor.isEmpty()) {
         continue;
       }
-      StartupClass.Builder<DexType> startupClassBuilder = StartupClass.builder();
+      StartupClass.Builder<DexType, DexMethod> startupClassBuilder = StartupClass.builder();
       startupDescriptor = parseSyntheticFlag(startupDescriptor, startupClassBuilder);
-      int methodNameStartIndex = getMethodNameStartIndex(startupDescriptor);
-      if (methodNameStartIndex >= 0) {
-        DexMethod startupMethod =
-            parseStartupMethodDescriptor(startupDescriptor, methodNameStartIndex, dexItemFactory);
-        if (startupMethod != null) {
-          startupClasses.add(
-              startupClassBuilder.setReference(startupMethod.getHolderType()).build());
-          startupMethods.add(startupMethod);
-        } else {
-          reporter.warning(
-              new StringDiagnostic("Invalid descriptor for startup method: " + startupDescriptor));
-        }
-      } else {
-        DexType startupClass = parseStartupClassDescriptor(startupDescriptor, dexItemFactory);
-        if (startupClass != null) {
-          startupClasses.add(startupClassBuilder.setReference(startupClass).build());
-        } else {
-          reporter.warning(
-              new StringDiagnostic("Invalid descriptor for startup class: " + startupDescriptor));
-        }
-      }
+      parseStartupClassOrMethod(
+          startupDescriptor,
+          dexItemFactory,
+          startupClass ->
+              startupClasses.add(startupClassBuilder.setClassReference(startupClass).build()),
+          // TODO(b/238173796): Startup methods should be added as startup methods.
+          startupMethod ->
+              startupClasses.add(
+                  startupClassBuilder.setClassReference(startupMethod.getHolderType()).build()),
+          actual ->
+              reporter.warning(
+                  new StringDiagnostic(
+                      "Invalid descriptor for startup class or method: " + actual)));
     }
-    return new StartupConfiguration(startupClasses, startupMethods);
+    return new StartupConfiguration(startupClasses);
   }
 
   public static String parseSyntheticFlag(
-      String startupDescriptor, StartupClass.Builder<?> startupClassBuilder) {
+      String startupDescriptor, StartupItem.Builder<?, ?, ?> startupItemBuilder) {
     if (!startupDescriptor.isEmpty() && startupDescriptor.charAt(0) == 'S') {
-      startupClassBuilder.setSynthetic();
+      startupItemBuilder.setSynthetic();
       return startupDescriptor.substring(1);
     }
     return startupDescriptor;
   }
 
-  private static int getMethodNameStartIndex(String startupDescriptor) {
-    int arrowIndex = startupDescriptor.indexOf("->");
-    return arrowIndex >= 0 ? arrowIndex + 2 : arrowIndex;
+  public static void parseStartupClassOrMethod(
+      String startupDescriptor,
+      DexItemFactory dexItemFactory,
+      Consumer<DexType> startupClassConsumer,
+      Consumer<DexMethod> startupMethodConsumer,
+      Consumer<String> parseErrorHandler) {
+    int arrowStartIndex = getArrowStartIndex(startupDescriptor);
+    if (arrowStartIndex >= 0) {
+      DexMethod startupMethod =
+          parseStartupMethodDescriptor(startupDescriptor, arrowStartIndex, dexItemFactory);
+      if (startupMethod != null) {
+        startupMethodConsumer.accept(startupMethod);
+      } else {
+        parseErrorHandler.accept(startupDescriptor);
+      }
+    } else {
+      DexType startupClass = parseStartupClassDescriptor(startupDescriptor, dexItemFactory);
+      if (startupClass != null) {
+        startupClassConsumer.accept(startupClass);
+      } else {
+        parseErrorHandler.accept(startupDescriptor);
+      }
+    }
+  }
+
+  private static int getArrowStartIndex(String startupDescriptor) {
+    return startupDescriptor.indexOf("->");
   }
 
   private static DexType parseStartupClassDescriptor(
@@ -136,13 +148,14 @@
   }
 
   private static DexMethod parseStartupMethodDescriptor(
-      String startupMethodDescriptor, int methodNameStartIndex, DexItemFactory dexItemFactory) {
-    String classDescriptor = startupMethodDescriptor.substring(0, methodNameStartIndex - 2);
+      String startupMethodDescriptor, int arrowStartIndex, DexItemFactory dexItemFactory) {
+    String classDescriptor = startupMethodDescriptor.substring(0, arrowStartIndex);
     DexType classType = parseStartupClassDescriptor(classDescriptor, dexItemFactory);
     if (classType == null) {
       return null;
     }
 
+    int methodNameStartIndex = arrowStartIndex + 2;
     String protoWithNameDescriptor = startupMethodDescriptor.substring(methodNameStartIndex);
     int methodNameEndIndex = protoWithNameDescriptor.indexOf('(');
     if (methodNameEndIndex <= 0) {
@@ -171,28 +184,45 @@
     return !startupClasses.isEmpty();
   }
 
-  public List<StartupClass<DexType>> getStartupClasses() {
+  public List<StartupClass<DexType, DexMethod>> getStartupClasses() {
     return startupClasses;
   }
 
   public static class Builder {
 
-    private final ImmutableList.Builder<StartupClass<DexType>> startupClassesBuilder =
+    private final ImmutableList.Builder<StartupClass<DexType, DexMethod>> startupClassesBuilder =
         ImmutableList.builder();
-    private final ImmutableList.Builder<DexMethod> startupMethodsBuilder = ImmutableList.builder();
 
-    public Builder addStartupClass(StartupClass<DexType> startupClass) {
+    public Builder addStartupItem(StartupItem<DexType, DexMethod, ?> startupItem) {
+      if (startupItem.isStartupClass()) {
+        return addStartupClass(startupItem.asStartupClass());
+      } else {
+        assert startupItem.isStartupMethod();
+        return addStartupMethod(startupItem.asStartupMethod());
+      }
+    }
+
+    public Builder addStartupClass(StartupClass<DexType, DexMethod> startupClass) {
       this.startupClassesBuilder.add(startupClass);
       return this;
     }
 
+    public Builder addStartupMethod(StartupMethod<DexType, DexMethod> startupMethod) {
+      // TODO(b/238173796): Startup methods should be added as startup methods.
+      return addStartupClass(
+          StartupClass.dexBuilder()
+              .setFlags(startupMethod.getFlags())
+              .setClassReference(startupMethod.getReference().getHolderType())
+              .build());
+    }
+
     public Builder apply(Consumer<Builder> consumer) {
       consumer.accept(this);
       return this;
     }
 
     public StartupConfiguration build() {
-      return new StartupConfiguration(startupClassesBuilder.build(), startupMethodsBuilder.build());
+      return new StartupConfiguration(startupClassesBuilder.build());
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java
index 804cbca..0678602 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue.DexValueBoolean;
 import com.android.tools.r8.graph.DexValue.DexValueString;
@@ -105,11 +104,11 @@
   }
 
   private void instrumentClass(DexProgramClass clazz) {
-    ProgramMethod classInitializer = ensureClassInitializer(clazz);
-    instrumentClassInitializer(classInitializer);
+    ensureClassInitializer(clazz);
+    clazz.forEachProgramMethod(this::instrumentMethod);
   }
 
-  private ProgramMethod ensureClassInitializer(DexProgramClass clazz) {
+  private void ensureClassInitializer(DexProgramClass clazz) {
     if (!clazz.hasClassInitializer()) {
       ComputedApiLevel computedApiLevel =
           appView.apiLevelCompute().computeInitialMinApiLevel(appView.options());
@@ -125,22 +124,22 @@
               .setMethod(dexItemFactory.createClassInitializer(clazz.getType()))
               .build());
     }
-    return clazz.getProgramClassInitializer();
   }
 
-  private void instrumentClassInitializer(ProgramMethod method) {
-    DexString descriptor;
+  private void instrumentMethod(ProgramMethod method) {
     DexMethod methodToInvoke;
+    DexMethod methodToPrint;
     SyntheticItems syntheticItems = appView.getSyntheticItems();
     if (syntheticItems.isSyntheticClass(method.getHolder())) {
       Collection<DexType> synthesizingContexts =
           syntheticItems.getSynthesizingContextTypes(method.getHolderType());
       assert synthesizingContexts.size() == 1;
-      descriptor = synthesizingContexts.iterator().next().getDescriptor();
+      DexType synthesizingContext = synthesizingContexts.iterator().next();
       methodToInvoke = references.addSyntheticMethod;
+      methodToPrint = method.getReference().withHolder(synthesizingContext, dexItemFactory);
     } else {
-      descriptor = method.getHolderType().getDescriptor();
       methodToInvoke = references.addNonSyntheticMethod;
+      methodToPrint = method.getReference();
     }
 
     IRCode code = method.buildIR(appView);
@@ -148,7 +147,8 @@
     instructionIterator.positionBeforeNextInstructionThatMatches(not(Instruction::isArgument));
 
     Value descriptorValue =
-        instructionIterator.insertConstStringInstruction(appView, code, descriptor);
+        instructionIterator.insertConstStringInstruction(
+            appView, code, dexItemFactory.createString(methodToPrint.toSmaliString()));
     instructionIterator.add(
         InvokeStatic.builder()
             .setMethod(methodToInvoke)
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupItem.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupItem.java
new file mode 100644
index 0000000..ada502c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupItem.java
@@ -0,0 +1,134 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.experimental.startup;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import java.util.function.Consumer;
+
+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 boolean isStartupClass() {
+    return false;
+  }
+
+  public StartupClass<C, M> asStartupClass() {
+    return null;
+  }
+
+  public boolean isStartupMethod() {
+    return false;
+  }
+
+  public StartupMethod<C, M> asStartupMethod() {
+    return null;
+  }
+
+  public static <C, M> Builder<C, M, ?> builder() {
+    return new Builder<>();
+  }
+
+  public static Builder<DexType, DexMethod, ?> dexBuilder() {
+    return new Builder<>();
+  }
+
+  public int getFlags() {
+    return flags;
+  }
+
+  public R getReference() {
+    return reference;
+  }
+
+  public boolean isSynthetic() {
+    return (flags & FLAG_SYNTHETIC) != 0;
+  }
+
+  @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 StartupItem<C, M, ?> build() {
+      if (classReference != null) {
+        return new StartupClass<>(flags, classReference);
+      } else {
+        assert methodReference != null;
+        return new StartupMethod<>(flags, methodReference);
+      }
+    }
+
+    public B self() {
+      return (B) this;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupMethod.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupMethod.java
new file mode 100644
index 0000000..ded8403
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupMethod.java
@@ -0,0 +1,44 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.experimental.startup;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.experimental.startup.StartupClass.Builder;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+
+public class StartupMethod<C, M> extends StartupItem<C, M, M> {
+
+  public StartupMethod(int flags, M reference) {
+    super(flags, reference);
+  }
+
+  public static Builder<ClassReference, MethodReference> referenceBuilder() {
+    return new Builder<>();
+  }
+
+  @Override
+  public boolean isStartupMethod() {
+    return true;
+  }
+
+  @Override
+  public StartupMethod<C, M> asStartupMethod() {
+    return this;
+  }
+
+  public static class Builder<C, M> extends StartupItem.Builder<C, M, Builder<C, M>> {
+
+    @Override
+    public Builder<C, M> setClassReference(C reference) {
+      throw new Unreachable();
+    }
+
+    @Override
+    public StartupMethod<C, M> build() {
+      return new StartupMethod<>(flags, methodReference);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java
index 5d67d8c..1676991 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
@@ -5,6 +5,7 @@
 package com.android.tools.r8.experimental.startup;
 
 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;
@@ -35,7 +36,7 @@
 
   public abstract boolean contains(DexType type, SyntheticItems syntheticItems);
 
-  public abstract Collection<StartupClass<DexType>> getClasses();
+  public abstract Collection<StartupClass<DexType, DexMethod>> getClasses();
 
   public abstract boolean isEmpty();
 
diff --git a/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java b/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java
index 31c7d4c..e5af39a 100644
--- a/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java
+++ b/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfFrame;
@@ -21,6 +22,7 @@
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfLabel;
 import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfMonitor;
 import com.android.tools.r8.cf.code.CfNew;
 import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.cf.code.CfReturnVoid;
@@ -47,6 +49,7 @@
 import com.android.tools.r8.graph.MethodCollection.MethodCollectionFactory;
 import com.android.tools.r8.graph.NestHostClassAttribute;
 import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.Monitor;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.origin.Origin;
 import com.google.common.collect.ImmutableList;
@@ -87,8 +90,8 @@
               dexItemFactory.createField(
                   dexItemFactory.createType(
                       "Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
-                  dexItemFactory.createType("Ljava/lang/StringBuilder;"),
-                  dexItemFactory.createString("builder")))
+                  dexItemFactory.createType("Ljava/util/LinkedHashSet;"),
+                  dexItemFactory.createString("lines")))
           .setAccessFlags(FieldAccessFlags.fromCfAccessFlags(18))
           .setApiLevel(ComputedApiLevel.unknown())
           .build()
@@ -192,7 +195,7 @@
           .setCode(method -> createCfCode4_addSyntheticMethod(dexItemFactory, method))
           .build(),
       DexEncodedMethod.syntheticBuilder()
-          .setAccessFlags(MethodAccessFlags.fromCfAccessFlags(34, false))
+          .setAccessFlags(MethodAccessFlags.fromCfAccessFlags(2, false))
           .setApiLevelForCode(ComputedApiLevel.unknown())
           .setApiLevelForDefinition(ComputedApiLevel.unknown())
           .setClassFileVersion(CfVersion.V1_8)
@@ -240,7 +243,7 @@
   private static DexEncodedMethod[] createVirtualMethods(DexItemFactory dexItemFactory) {
     return new DexEncodedMethod[] {
       DexEncodedMethod.syntheticBuilder()
-          .setAccessFlags(MethodAccessFlags.fromCfAccessFlags(33, false))
+          .setAccessFlags(MethodAccessFlags.fromCfAccessFlags(1, false))
           .setApiLevelForCode(ComputedApiLevel.unknown())
           .setApiLevelForDefinition(ComputedApiLevel.unknown())
           .setClassFileVersion(CfVersion.V1_8)
@@ -305,20 +308,20 @@
                 false),
             label1,
             new CfLoad(ValueType.OBJECT, 0),
-            new CfNew(factory.stringBuilderType),
+            new CfNew(factory.createType("Ljava/util/LinkedHashSet;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 factory.createMethod(
-                    factory.stringBuilderType,
+                    factory.createType("Ljava/util/LinkedHashSet;"),
                     factory.createProto(factory.voidType),
                     factory.createString("<init>")),
                 false),
             new CfInstanceFieldWrite(
                 factory.createField(
                     factory.createType("Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
-                    factory.stringBuilderType,
-                    factory.createString("builder"))),
+                    factory.createType("Ljava/util/LinkedHashSet;"),
+                    factory.createString("lines"))),
             label2,
             new CfReturnVoid(),
             label3),
@@ -332,30 +335,84 @@
     CfLabel label2 = new CfLabel();
     CfLabel label3 = new CfLabel();
     CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    CfLabel label6 = new CfLabel();
+    CfLabel label7 = new CfLabel();
+    CfLabel label8 = new CfLabel();
+    CfLabel label9 = new CfLabel();
+    CfLabel label10 = new CfLabel();
+    CfLabel label11 = new CfLabel();
     return new CfCode(
         method.holder,
         2,
-        2,
+        4,
         ImmutableList.of(
             label0,
-            new CfStaticFieldRead(
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
                 factory.createField(
                     factory.createType("Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
-                    factory.booleanType,
-                    factory.createString("writeToLogcat"))),
-            new CfIf(If.Type.EQ, ValueType.INT, label2),
+                    factory.createType("Ljava/util/LinkedHashSet;"),
+                    factory.createString("lines"))),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfStore(ValueType.OBJECT, 2),
+            new CfMonitor(Monitor.Type.ENTER),
             label1,
             new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
+                    factory.createType("Ljava/util/LinkedHashSet;"),
+                    factory.createString("lines"))),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInvoke(
-                183,
+                182,
                 factory.createMethod(
-                    factory.createType("Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
-                    factory.createProto(factory.voidType, factory.stringType),
-                    factory.createString("writeToLogcat")),
+                    factory.createType("Ljava/util/LinkedHashSet;"),
+                    factory.createProto(factory.booleanType, factory.objectType),
+                    factory.createString("add")),
                 false),
-            new CfGoto(label3),
+            new CfIf(If.Type.NE, ValueType.INT, label4),
             label2,
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfMonitor(Monitor.Type.EXIT),
+            label3,
+            new CfReturnVoid(),
+            label4,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType(
+                              "Lcom/android/tools/r8/startup/InstrumentationServerImpl;")),
+                      FrameType.initializedNonNullReference(factory.stringType),
+                      FrameType.initializedNonNullReference(factory.objectType)
+                    })),
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfMonitor(Monitor.Type.EXIT),
+            label5,
+            new CfGoto(label8),
+            label6,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType(
+                              "Lcom/android/tools/r8/startup/InstrumentationServerImpl;")),
+                      FrameType.initializedNonNullReference(factory.stringType),
+                      FrameType.initializedNonNullReference(factory.objectType)
+                    }),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initializedNonNullReference(factory.throwableType)))),
+            new CfStore(ValueType.OBJECT, 3),
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfMonitor(Monitor.Type.EXIT),
+            label7,
+            new CfLoad(ValueType.OBJECT, 3),
+            new CfThrow(),
+            label8,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
@@ -365,30 +422,23 @@
                               "Lcom/android/tools/r8/startup/InstrumentationServerImpl;")),
                       FrameType.initializedNonNullReference(factory.stringType)
                     })),
-            new CfLoad(ValueType.OBJECT, 0),
-            new CfInstanceFieldRead(
+            new CfStaticFieldRead(
                 factory.createField(
                     factory.createType("Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
-                    factory.stringBuilderType,
-                    factory.createString("builder"))),
+                    factory.booleanType,
+                    factory.createString("writeToLogcat"))),
+            new CfIf(If.Type.EQ, ValueType.INT, label10),
+            label9,
+            new CfLoad(ValueType.OBJECT, 0),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInvoke(
-                182,
+                183,
                 factory.createMethod(
-                    factory.stringBuilderType,
-                    factory.createProto(factory.stringBuilderType, factory.stringType),
-                    factory.createString("append")),
+                    factory.createType("Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
+                    factory.createProto(factory.voidType, factory.stringType),
+                    factory.createString("writeToLogcat")),
                 false),
-            new CfConstNumber(10, ValueType.INT),
-            new CfInvoke(
-                182,
-                factory.createMethod(
-                    factory.stringBuilderType,
-                    factory.createProto(factory.stringBuilderType, factory.charType),
-                    factory.createString("append")),
-                false),
-            new CfStackInstruction(CfStackInstruction.Opcode.Pop),
-            label3,
+            label10,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
@@ -399,8 +449,14 @@
                       FrameType.initializedNonNullReference(factory.stringType)
                     })),
             new CfReturnVoid(),
-            label4),
-        ImmutableList.of(),
+            label11),
+        ImmutableList.of(
+            new CfTryCatch(
+                label1, label3, ImmutableList.of(factory.throwableType), ImmutableList.of(label6)),
+            new CfTryCatch(
+                label4, label5, ImmutableList.of(factory.throwableType), ImmutableList.of(label6)),
+            new CfTryCatch(
+                label6, label7, ImmutableList.of(factory.throwableType), ImmutableList.of(label6))),
         ImmutableList.of());
   }
 
@@ -531,102 +587,146 @@
     CfLabel label5 = new CfLabel();
     CfLabel label6 = new CfLabel();
     CfLabel label7 = new CfLabel();
+    CfLabel label8 = new CfLabel();
+    CfLabel label9 = new CfLabel();
+    CfLabel label10 = new CfLabel();
+    CfLabel label11 = new CfLabel();
+    CfLabel label12 = new CfLabel();
+    CfLabel label13 = new CfLabel();
+    CfLabel label14 = new CfLabel();
+    CfLabel label15 = new CfLabel();
+    CfLabel label16 = new CfLabel();
     return new CfCode(
         method.holder,
-        3,
         4,
+        8,
         ImmutableList.of(
             label0,
-            new CfNew(factory.createType("Ljava/io/FileOutputStream;")),
+            new CfNew(factory.createType("Ljava/io/PrintWriter;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.OBJECT, 1),
+            new CfConstString(factory.createString("UTF-8")),
             new CfInvoke(
                 183,
                 factory.createMethod(
-                    factory.createType("Ljava/io/FileOutputStream;"),
-                    factory.createProto(factory.voidType, factory.createType("Ljava/io/File;")),
+                    factory.createType("Ljava/io/PrintWriter;"),
+                    factory.createProto(
+                        factory.voidType, factory.createType("Ljava/io/File;"), factory.stringType),
                     factory.createString("<init>")),
                 false),
             new CfStore(ValueType.OBJECT, 2),
             label1,
-            new CfLoad(ValueType.OBJECT, 2),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInstanceFieldRead(
                 factory.createField(
                     factory.createType("Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
-                    factory.stringBuilderType,
-                    factory.createString("builder"))),
-            new CfInvoke(
-                182,
-                factory.createMethod(
-                    factory.stringBuilderType,
-                    factory.createProto(factory.stringType),
-                    factory.createString("toString")),
-                false),
-            new CfConstString(factory.createString("UTF-8")),
-            new CfInvoke(
-                184,
-                factory.createMethod(
-                    factory.createType("Ljava/nio/charset/Charset;"),
-                    factory.createProto(
-                        factory.createType("Ljava/nio/charset/Charset;"), factory.stringType),
-                    factory.createString("forName")),
-                false),
-            new CfInvoke(
-                182,
-                factory.createMethod(
-                    factory.stringType,
-                    factory.createProto(
-                        factory.byteArrayType, factory.createType("Ljava/nio/charset/Charset;")),
-                    factory.createString("getBytes")),
-                false),
-            new CfInvoke(
-                182,
-                factory.createMethod(
-                    factory.createType("Ljava/io/FileOutputStream;"),
-                    factory.createProto(factory.voidType, factory.byteArrayType),
-                    factory.createString("write")),
-                false),
+                    factory.createType("Ljava/util/LinkedHashSet;"),
+                    factory.createString("lines"))),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfStore(ValueType.OBJECT, 3),
+            new CfMonitor(Monitor.Type.ENTER),
             label2,
-            new CfLoad(ValueType.OBJECT, 2),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
+                    factory.createType("Ljava/util/LinkedHashSet;"),
+                    factory.createString("lines"))),
             new CfInvoke(
                 182,
                 factory.createMethod(
-                    factory.createType("Ljava/io/FileOutputStream;"),
-                    factory.createProto(factory.voidType),
-                    factory.createString("close")),
+                    factory.createType("Ljava/util/LinkedHashSet;"),
+                    factory.createProto(factory.createType("Ljava/util/Iterator;")),
+                    factory.createString("iterator")),
                 false),
+            new CfStore(ValueType.OBJECT, 4),
             label3,
-            new CfGoto(label6),
-            label4,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
-                    new int[] {0, 1, 2},
+                    new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
                       FrameType.initializedNonNullReference(
                           factory.createType(
                               "Lcom/android/tools/r8/startup/InstrumentationServerImpl;")),
                       FrameType.initializedNonNullReference(factory.createType("Ljava/io/File;")),
                       FrameType.initializedNonNullReference(
-                          factory.createType("Ljava/io/FileOutputStream;"))
-                    }),
-                new ArrayDeque<>(
-                    Arrays.asList(FrameType.initializedNonNullReference(factory.throwableType)))),
-            new CfStore(ValueType.OBJECT, 3),
+                          factory.createType("Ljava/io/PrintWriter;")),
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Ljava/util/Iterator;"))
+                    })),
+            new CfLoad(ValueType.OBJECT, 4),
+            new CfInvoke(
+                185,
+                factory.createMethod(
+                    factory.createType("Ljava/util/Iterator;"),
+                    factory.createProto(factory.booleanType),
+                    factory.createString("hasNext")),
+                true),
+            new CfIf(If.Type.EQ, ValueType.INT, label6),
+            new CfLoad(ValueType.OBJECT, 4),
+            new CfInvoke(
+                185,
+                factory.createMethod(
+                    factory.createType("Ljava/util/Iterator;"),
+                    factory.createProto(factory.objectType),
+                    factory.createString("next")),
+                true),
+            new CfCheckCast(factory.stringType),
+            new CfStore(ValueType.OBJECT, 5),
+            label4,
             new CfLoad(ValueType.OBJECT, 2),
+            new CfLoad(ValueType.OBJECT, 5),
             new CfInvoke(
                 182,
                 factory.createMethod(
-                    factory.createType("Ljava/io/FileOutputStream;"),
-                    factory.createProto(factory.voidType),
-                    factory.createString("close")),
+                    factory.createType("Ljava/io/PrintWriter;"),
+                    factory.createProto(factory.voidType, factory.stringType),
+                    factory.createString("println")),
                 false),
             label5,
-            new CfLoad(ValueType.OBJECT, 3),
-            new CfThrow(),
+            new CfGoto(label3),
             label6,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType(
+                              "Lcom/android/tools/r8/startup/InstrumentationServerImpl;")),
+                      FrameType.initializedNonNullReference(factory.createType("Ljava/io/File;")),
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Ljava/io/PrintWriter;")),
+                      FrameType.initializedNonNullReference(factory.objectType)
+                    })),
+            new CfLoad(ValueType.OBJECT, 3),
+            new CfMonitor(Monitor.Type.EXIT),
+            label7,
+            new CfGoto(label10),
+            label8,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType(
+                              "Lcom/android/tools/r8/startup/InstrumentationServerImpl;")),
+                      FrameType.initializedNonNullReference(factory.createType("Ljava/io/File;")),
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Ljava/io/PrintWriter;")),
+                      FrameType.initializedNonNullReference(factory.objectType)
+                    }),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initializedNonNullReference(factory.throwableType)))),
+            new CfStore(ValueType.OBJECT, 6),
+            new CfLoad(ValueType.OBJECT, 3),
+            new CfMonitor(Monitor.Type.EXIT),
+            label9,
+            new CfLoad(ValueType.OBJECT, 6),
+            new CfThrow(),
+            label10,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
                     new FrameType[] {
                       FrameType.initializedNonNullReference(
@@ -634,13 +734,74 @@
                               "Lcom/android/tools/r8/startup/InstrumentationServerImpl;")),
                       FrameType.initializedNonNullReference(factory.createType("Ljava/io/File;")),
                       FrameType.initializedNonNullReference(
-                          factory.createType("Ljava/io/FileOutputStream;"))
+                          factory.createType("Ljava/io/PrintWriter;"))
+                    })),
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Ljava/io/PrintWriter;"),
+                    factory.createProto(factory.voidType),
+                    factory.createString("close")),
+                false),
+            label11,
+            new CfGoto(label15),
+            label12,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType(
+                              "Lcom/android/tools/r8/startup/InstrumentationServerImpl;")),
+                      FrameType.initializedNonNullReference(factory.createType("Ljava/io/File;")),
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Ljava/io/PrintWriter;"))
+                    }),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initializedNonNullReference(factory.throwableType)))),
+            new CfStore(ValueType.OBJECT, 7),
+            label13,
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Ljava/io/PrintWriter;"),
+                    factory.createProto(factory.voidType),
+                    factory.createString("close")),
+                false),
+            label14,
+            new CfLoad(ValueType.OBJECT, 7),
+            new CfThrow(),
+            label15,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType(
+                              "Lcom/android/tools/r8/startup/InstrumentationServerImpl;")),
+                      FrameType.initializedNonNullReference(factory.createType("Ljava/io/File;")),
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Ljava/io/PrintWriter;"))
                     })),
             new CfReturnVoid(),
-            label7),
+            label16),
         ImmutableList.of(
             new CfTryCatch(
-                label1, label2, ImmutableList.of(factory.throwableType), ImmutableList.of(label4))),
+                label2, label7, ImmutableList.of(factory.throwableType), ImmutableList.of(label8)),
+            new CfTryCatch(
+                label8, label9, ImmutableList.of(factory.throwableType), ImmutableList.of(label8)),
+            new CfTryCatch(
+                label1,
+                label10,
+                ImmutableList.of(factory.throwableType),
+                ImmutableList.of(label12)),
+            new CfTryCatch(
+                label12,
+                label13,
+                ImmutableList.of(factory.throwableType),
+                ImmutableList.of(label12))),
         ImmutableList.of());
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
index 9818d3e..037fafb 100644
--- a/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
 import com.google.common.collect.ImmutableList;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.Iterator;
 
@@ -55,6 +56,22 @@
         return method.getFormalTypes().size() - other.getFormalTypes().size();
       };
 
+  public static MethodReference classConstructor(Class<?> clazz) {
+    return classConstructor(Reference.classFromClass(clazz));
+  }
+
+  public static MethodReference classConstructor(ClassReference type) {
+    return Reference.classConstructor(type);
+  }
+
+  public static MethodReference instanceConstructor(Class<?> clazz) {
+    return instanceConstructor(Reference.classFromClass(clazz));
+  }
+
+  public static MethodReference instanceConstructor(ClassReference type) {
+    return Reference.method(type, "<init>", Collections.emptyList(), null);
+  }
+
   public static int compare(MethodReference methodReference, ClassReference other) {
     return ClassReferenceUtils.compare(other, methodReference) * -1;
   }
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
index 59d2bc6..cd34cd5 100644
--- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -133,6 +133,10 @@
     return withDexRuntimeFilter(vm -> true);
   }
 
+  public TestParametersBuilder withDexRuntimesAndAllApiLevels() {
+    return withDexRuntimes().withAllApiLevels();
+  }
+
   /** Add specific runtime if available. */
   public TestParametersBuilder withDexRuntime(DexVm.Version runtime) {
     return withDexRuntimeFilter(vm -> vm == runtime);
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 e77e3d9..2a325ad 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
@@ -10,7 +10,6 @@
 import com.android.tools.r8.experimental.startup.StartupClass;
 import com.android.tools.r8.experimental.startup.StartupConfiguration;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
 import com.google.common.collect.ImmutableList;
 import java.util.Collections;
 import java.util.List;
@@ -54,8 +53,8 @@
                                   startupClasses.forEach(
                                       startupClass ->
                                           builder.addStartupClass(
-                                              StartupClass.<DexType>builder()
-                                                  .setReference(
+                                              StartupClass.dexBuilder()
+                                                  .setClassReference(
                                                       toDexType(startupClass, dexItemFactory))
                                                   .build())))
                           .build());
diff --git a/src/test/java/com/android/tools/r8/startup/InstrumentationServerImpl.java b/src/test/java/com/android/tools/r8/startup/InstrumentationServerImpl.java
index 6896f3f..328b485 100644
--- a/src/test/java/com/android/tools/r8/startup/InstrumentationServerImpl.java
+++ b/src/test/java/com/android/tools/r8/startup/InstrumentationServerImpl.java
@@ -5,9 +5,9 @@
 package com.android.tools.r8.startup;
 
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.nio.charset.Charset;
+import java.io.PrintWriter;
+import java.util.LinkedHashSet;
 
 public class InstrumentationServerImpl extends InstrumentationServer {
 
@@ -17,7 +17,7 @@
   private static boolean writeToLogcat;
   private static String logcatTag;
 
-  private final StringBuilder builder = new StringBuilder();
+  private final LinkedHashSet<String> lines = new LinkedHashSet<>();
 
   private InstrumentationServerImpl() {}
 
@@ -33,21 +33,28 @@
     getInstance().addLine('S' + descriptor);
   }
 
-  private synchronized void addLine(String line) {
+  private void addLine(String line) {
+    synchronized (lines) {
+      if (!lines.add(line)) {
+        return;
+      }
+    }
     if (writeToLogcat) {
       writeToLogcat(line);
-    } else {
-      builder.append(line).append('\n');
     }
   }
 
   @Override
-  public synchronized void writeToFile(File file) throws IOException {
-    FileOutputStream stream = new FileOutputStream(file);
+  public void writeToFile(File file) throws IOException {
+    PrintWriter writer = new PrintWriter(file, "UTF-8");
     try {
-      stream.write(builder.toString().getBytes(Charset.forName("UTF-8")));
+      synchronized (lines) {
+        for (String line : lines) {
+          writer.println(line);
+        }
+      }
     } finally {
-      stream.close();
+      writer.close();
     }
   }
 
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 4419055..d57a5ab 100644
--- a/src/test/java/com/android/tools/r8/startup/MinimalStartupDexTest.java
+++ b/src/test/java/com/android/tools/r8/startup/MinimalStartupDexTest.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.experimental.startup.StartupClass;
 import com.android.tools.r8.experimental.startup.StartupConfiguration;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -53,12 +52,13 @@
                     .setStartupConfiguration(
                         StartupConfiguration.builder()
                             .addStartupClass(
-                                StartupClass.<DexType>builder()
-                                    .setReference(toDexType(Main.class, options.dexItemFactory()))
+                                StartupClass.dexBuilder()
+                                    .setClassReference(
+                                        toDexType(Main.class, options.dexItemFactory()))
                                     .build())
                             .addStartupClass(
-                                StartupClass.<DexType>builder()
-                                    .setReference(
+                                StartupClass.dexBuilder()
+                                    .setClassReference(
                                         toDexType(AStartupClass.class, options.dexItemFactory()))
                                     .build())
                             .build()))
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 1891b6e..a065a8d 100644
--- a/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java
+++ b/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java
@@ -5,17 +5,18 @@
 package com.android.tools.r8.startup;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.experimental.startup.StartupClass;
+import com.android.tools.r8.experimental.startup.StartupItem;
+import com.android.tools.r8.experimental.startup.StartupMethod;
 import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.startup.StartupSyntheticPlacementTest.Main;
 import com.android.tools.r8.startup.utils.StartupTestingUtils;
+import com.android.tools.r8.utils.MethodReferenceUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.List;
@@ -33,13 +34,12 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+    return getTestParameters().withDexRuntimesAndAllApiLevels().build();
   }
 
   @Test
-  public void testD8() throws Exception {
-    assumeTrue(parameters.isDexRuntime());
-    List<StartupClass<ClassReference>> startupList = new ArrayList<>();
+  public void test() throws Exception {
+    List<StartupItem<ClassReference, MethodReference, ?>> startupList = new ArrayList<>();
     testForD8(parameters.getBackend())
         .addInnerClasses(getClass())
         .apply(StartupTestingUtils.enableStartupInstrumentation(parameters))
@@ -48,7 +48,7 @@
         .compile()
         .addRunClasspathFiles(StartupTestingUtils.getAndroidUtilLog(temp))
         .run(parameters.getRuntime(), Main.class)
-        .apply(StartupTestingUtils.removeStartupClassesFromStdout(startupList::add))
+        .apply(StartupTestingUtils.removeStartupListFromStdout(startupList::add))
         .assertSuccessWithOutputLines(getExpectedOutput());
     assertEquals(getExpectedStartupList(), startupList);
   }
@@ -57,13 +57,21 @@
     return ImmutableList.of("foo");
   }
 
-  private List<StartupClass<ClassReference>> getExpectedStartupList() {
+  private List<StartupMethod<ClassReference, MethodReference>> getExpectedStartupList()
+      throws NoSuchMethodException {
     return ImmutableList.of(
-        StartupClass.<ClassReference>builder()
-            .setReference(Reference.classFromClass(Main.class))
+        StartupMethod.referenceBuilder()
+            .setMethodReference(MethodReferenceUtils.classConstructor(Main.class))
             .build(),
-        StartupClass.<ClassReference>builder()
-            .setReference(Reference.classFromClass(AStartupClass.class))
+        StartupMethod.referenceBuilder()
+            .setMethodReference(MethodReferenceUtils.mainMethod(Main.class))
+            .build(),
+        StartupMethod.referenceBuilder()
+            .setMethodReference(MethodReferenceUtils.classConstructor(AStartupClass.class))
+            .build(),
+        StartupMethod.referenceBuilder()
+            .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 ce9a337..41aff80 100644
--- a/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java
+++ b/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java
@@ -13,15 +13,18 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.experimental.startup.StartupClass;
+import com.android.tools.r8.experimental.startup.StartupItem;
+import com.android.tools.r8.experimental.startup.StartupMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.startup.utils.MixedSectionLayoutInspector;
 import com.android.tools.r8.startup.utils.StartupTestingUtils;
 import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.MethodReferenceUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
@@ -57,7 +60,7 @@
 
   @Test
   public void test() throws Exception {
-    List<StartupClass<ClassReference>> startupList = new ArrayList<>();
+    List<StartupItem<ClassReference, MethodReference, ?>> startupList = new ArrayList<>();
     testForD8(parameters.getBackend())
         .addInnerClasses(getClass())
         .apply(StartupTestingUtils.enableStartupInstrumentation(parameters))
@@ -66,7 +69,7 @@
         .compile()
         .addRunClasspathFiles(StartupTestingUtils.getAndroidUtilLog(temp))
         .run(parameters.getRuntime(), Main.class, Boolean.toString(useLambda))
-        .apply(StartupTestingUtils.removeStartupClassesFromStdout(startupList::add))
+        .apply(StartupTestingUtils.removeStartupListFromStdout(startupList::add))
         .assertSuccessWithOutputLines(getExpectedOutput());
     assertEquals(getExpectedStartupList(), startupList);
 
@@ -93,30 +96,60 @@
     return ImmutableList.of("A", "B", "C");
   }
 
-  private List<StartupClass<ClassReference>> getExpectedStartupList() {
-    ImmutableList.Builder<StartupClass<ClassReference>> builder = ImmutableList.builder();
+  private List<StartupMethod<ClassReference, MethodReference>> getExpectedStartupList()
+      throws NoSuchMethodException {
+    ImmutableList.Builder<StartupMethod<ClassReference, MethodReference>> builder =
+        ImmutableList.builder();
     builder.add(
-        StartupClass.<ClassReference>builder()
-            .setReference(Reference.classFromClass(Main.class))
-            .build());
-    builder.add(
-        StartupClass.<ClassReference>builder()
-            .setReference(Reference.classFromClass(A.class))
-            .build());
-    builder.add(
-        StartupClass.<ClassReference>builder()
-            .setReference(Reference.classFromClass(B.class))
+        StartupMethod.referenceBuilder()
+            .setMethodReference(MethodReferenceUtils.classConstructor(Main.class))
+            .build(),
+        StartupMethod.referenceBuilder()
+            .setMethodReference(MethodReferenceUtils.mainMethod(Main.class))
+            .build(),
+        StartupMethod.referenceBuilder()
+            .setMethodReference(MethodReferenceUtils.classConstructor(A.class))
+            .build(),
+        StartupMethod.referenceBuilder()
+            .setMethodReference(Reference.methodFromMethod(A.class.getDeclaredMethod("a")))
+            .build(),
+        StartupMethod.referenceBuilder()
+            .setMethodReference(MethodReferenceUtils.classConstructor(B.class))
+            .build(),
+        StartupMethod.referenceBuilder()
+            .setMethodReference(
+                Reference.methodFromMethod(B.class.getDeclaredMethod("b", boolean.class)))
             .build());
     if (useLambda) {
       builder.add(
-          StartupClass.<ClassReference>builder()
-              .setReference(Reference.classFromClass(B.class))
+          StartupMethod.referenceBuilder()
+              .setMethodReference(MethodReferenceUtils.classConstructor(B.class))
               .setSynthetic()
+              .build(),
+          StartupMethod.referenceBuilder()
+              .setMethodReference(MethodReferenceUtils.instanceConstructor(B.class))
+              .setSynthetic()
+              .build(),
+          StartupMethod.referenceBuilder()
+              .setMethodReference(
+                  Reference.method(
+                      Reference.classFromClass(B.class),
+                      "accept",
+                      ImmutableList.of(Reference.classFromClass(Object.class)),
+                      null))
+              .setSynthetic()
+              .build(),
+          StartupMethod.referenceBuilder()
+              .setMethodReference(
+                  Reference.methodFromMethod(B.class.getDeclaredMethod("lambda$b$0", Object.class)))
               .build());
     }
     builder.add(
-        StartupClass.<ClassReference>builder()
-            .setReference(Reference.classFromClass(C.class))
+        StartupMethod.referenceBuilder()
+            .setMethodReference(MethodReferenceUtils.classConstructor(C.class))
+            .build(),
+        StartupMethod.referenceBuilder()
+            .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 83c581c..0567f4b 100644
--- a/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java
+++ b/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java
@@ -14,19 +14,23 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.experimental.startup.StartupClass;
+import com.android.tools.r8.experimental.startup.StartupItem;
+import com.android.tools.r8.experimental.startup.StartupMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.startup.utils.MixedSectionLayoutInspector;
 import com.android.tools.r8.startup.utils.StartupTestingUtils;
 import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.MethodReferenceUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -53,7 +57,7 @@
 
   @Test
   public void test() throws Exception {
-    List<StartupClass<ClassReference>> startupList = new ArrayList<>();
+    List<StartupItem<ClassReference, MethodReference, ?>> startupList = new ArrayList<>();
     testForD8(parameters.getBackend())
         .addInnerClasses(getClass())
         .apply(StartupTestingUtils.enableStartupInstrumentation(parameters))
@@ -62,7 +66,7 @@
         .compile()
         .addRunClasspathFiles(StartupTestingUtils.getAndroidUtilLog(temp))
         .run(parameters.getRuntime(), Main.class)
-        .apply(StartupTestingUtils.removeStartupClassesFromStdout(startupList::add))
+        .apply(StartupTestingUtils.removeStartupListFromStdout(startupList::add))
         .assertSuccessWithOutputLines(getExpectedOutput());
     assertEquals(getExpectedStartupList(), startupList);
 
@@ -89,30 +93,50 @@
     return ImmutableList.of("A", "B", "C");
   }
 
-  private List<StartupClass<ClassReference>> getExpectedStartupList() {
-    ImmutableList.Builder<StartupClass<ClassReference>> builder = ImmutableList.builder();
-    builder.add(
-        StartupClass.<ClassReference>builder()
-            .setReference(Reference.classFromClass(Main.class))
-            .build());
-    builder.add(
-        StartupClass.<ClassReference>builder()
-            .setReference(Reference.classFromClass(A.class))
-            .build());
-    builder.add(
-        StartupClass.<ClassReference>builder()
-            .setReference(Reference.classFromClass(B.class))
-            .build());
-    builder.add(
-        StartupClass.<ClassReference>builder()
-            .setReference(Reference.classFromClass(B.class))
+  private List<StartupMethod<ClassReference, MethodReference>> getExpectedStartupList()
+      throws NoSuchMethodException {
+    return ImmutableList.of(
+        StartupMethod.referenceBuilder()
+            .setMethodReference(MethodReferenceUtils.classConstructor(Main.class))
+            .build(),
+        StartupMethod.referenceBuilder()
+            .setMethodReference(MethodReferenceUtils.mainMethod(Main.class))
+            .build(),
+        StartupMethod.referenceBuilder()
+            .setMethodReference(MethodReferenceUtils.classConstructor(A.class))
+            .build(),
+        StartupMethod.referenceBuilder()
+            .setMethodReference(Reference.methodFromMethod(A.class.getDeclaredMethod("a")))
+            .build(),
+        StartupMethod.referenceBuilder()
+            .setMethodReference(MethodReferenceUtils.classConstructor(B.class))
+            .build(),
+        StartupMethod.referenceBuilder()
+            .setMethodReference(Reference.methodFromMethod(B.class.getDeclaredMethod("b")))
+            .build(),
+        StartupMethod.referenceBuilder()
+            .setMethodReference(MethodReferenceUtils.classConstructor(B.class))
             .setSynthetic()
+            .build(),
+        StartupMethod.referenceBuilder()
+            .setMethodReference(MethodReferenceUtils.instanceConstructor(B.class))
+            .setSynthetic()
+            .build(),
+        StartupMethod.referenceBuilder()
+            .setMethodReference(
+                Reference.method(
+                    Reference.classFromClass(B.class), "run", Collections.emptyList(), null))
+            .setSynthetic()
+            .build(),
+        StartupMethod.referenceBuilder()
+            .setMethodReference(Reference.methodFromMethod(B.class.getDeclaredMethod("lambda$b$0")))
+            .build(),
+        StartupMethod.referenceBuilder()
+            .setMethodReference(MethodReferenceUtils.classConstructor(C.class))
+            .build(),
+        StartupMethod.referenceBuilder()
+            .setMethodReference(Reference.methodFromMethod(C.class.getDeclaredMethod("c")))
             .build());
-    builder.add(
-        StartupClass.<ClassReference>builder()
-            .setReference(Reference.classFromClass(C.class))
-            .build());
-    return builder.build();
   }
 
   private List<ClassReference> getExpectedClassDataLayout(int virtualFile) {
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 dad4d44..695bb3a 100644
--- a/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
+++ b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.startup.utils;
 
 import static com.android.tools.r8.TestBase.transformer;
+import static org.junit.Assert.fail;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.D8TestBuilder;
@@ -13,13 +14,16 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ThrowableConsumer;
-import com.android.tools.r8.experimental.startup.StartupClass;
 import com.android.tools.r8.experimental.startup.StartupConfiguration;
+import com.android.tools.r8.experimental.startup.StartupItem;
 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.Reference;
+import com.android.tools.r8.references.MethodReference;
 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;
@@ -59,22 +63,31 @@
         .writeToZip();
   }
 
-  public static ThrowingConsumer<D8TestRunResult, RuntimeException> removeStartupClassesFromStdout(
-      Consumer<StartupClass<ClassReference>> startupClassConsumer) {
-    return runResult -> removeStartupClassesFromStdout(runResult, startupClassConsumer);
+  public static ThrowingConsumer<D8TestRunResult, RuntimeException> removeStartupListFromStdout(
+      Consumer<StartupItem<ClassReference, MethodReference, ?>> startupItemConsumer) {
+    return runResult -> removeStartupListFromStdout(runResult, startupItemConsumer);
   }
 
-  public static void removeStartupClassesFromStdout(
-      D8TestRunResult runResult, Consumer<StartupClass<ClassReference>> startupClassConsumer) {
+  public static void removeStartupListFromStdout(
+      D8TestRunResult runResult,
+      Consumer<StartupItem<ClassReference, MethodReference, ?>> startupItemConsumer) {
+    DexItemFactory dexItemFactory = new DexItemFactory();
     StringBuilder stdoutBuilder = new StringBuilder();
     String startupDescriptorPrefix = "[" + startupInstrumentationTag + "] ";
     for (String line : StringUtils.splitLines(runResult.getStdOut(), true)) {
       if (line.startsWith(startupDescriptorPrefix)) {
-        StartupClass.Builder<ClassReference> startupClassBuilder = StartupClass.builder();
+        StartupItem.Builder<ClassReference, MethodReference, ?> startupItemBuilder =
+            StartupItem.builder();
         String message = line.substring(startupDescriptorPrefix.length());
-        message = StartupConfiguration.parseSyntheticFlag(message, startupClassBuilder);
-        startupClassBuilder.setReference(Reference.classFromDescriptor(message));
-        startupClassConsumer.accept(startupClassBuilder.build());
+        message = StartupConfiguration.parseSyntheticFlag(message, startupItemBuilder);
+        StartupConfiguration.parseStartupClassOrMethod(
+            message,
+            dexItemFactory,
+            startupClass -> startupItemBuilder.setClassReference(startupClass.asClassReference()),
+            startupMethod ->
+                startupItemBuilder.setMethodReference(startupMethod.asMethodReference()),
+            error -> fail("Unexpected parse error: " + error));
+        startupItemConsumer.accept(startupItemBuilder.build());
       } else {
         stdoutBuilder.append(line).append(System.lineSeparator());
       }
@@ -83,29 +96,41 @@
   }
 
   public static void setStartupConfiguration(
-      R8TestBuilder<?> testBuilder, List<StartupClass<ClassReference>> startupClasses) {
+      R8TestBuilder<?> testBuilder,
+      List<StartupItem<ClassReference, MethodReference, ?>> startupItems) {
     testBuilder.addOptionsModification(
         options -> {
           DexItemFactory dexItemFactory = options.dexItemFactory();
-          options
-              .getStartupOptions()
-              .setStartupConfiguration(
-                  StartupConfiguration.builder()
-                      .apply(
-                          builder ->
-                              startupClasses.forEach(
-                                  startupClass ->
-                                      builder.addStartupClass(
-                                          StartupClass.<DexType>builder()
-                                              .setFlags(startupClass.getFlags())
-                                              .setReference(
-                                                  dexItemFactory.createType(
-                                                      startupClass.getReference().getDescriptor()))
-                                              .build())))
-                      .build());
+          StartupConfiguration startupConfiguration =
+              StartupConfiguration.builder()
+                  .apply(
+                      builder ->
+                          startupItems.forEach(
+                              startupItem ->
+                                  builder.addStartupItem(
+                                      convertStartupItemToDex(startupItem, dexItemFactory))))
+                  .build();
+          options.getStartupOptions().setStartupConfiguration(startupConfiguration);
         });
   }
 
+  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();
   }