Implement ProfileAdditions using StartupProfile

Bug: b/271822426
Change-Id: I20a52ca8bb1c77136d959c056a2d2fcaef88c6c8
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 afab1b3..e848e3b 100644
--- a/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
+++ b/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
@@ -6,9 +6,9 @@
 
 import com.android.tools.r8.dex.FileWriter.MixedSectionOffsets;
 import com.android.tools.r8.experimental.startup.StartupProfile;
-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.StartupProfileClassRule;
+import com.android.tools.r8.experimental.startup.profile.StartupProfileMethodRule;
+import com.android.tools.r8.experimental.startup.profile.StartupProfileRule;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationDirectory;
@@ -82,7 +82,7 @@
             virtualFile.classes().size());
     LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(appView, true);
     StartupIndexedItemCollection indexedItemCollection = new StartupIndexedItemCollection();
-    for (StartupItem startupItem : startupProfileForWriting.getItems()) {
+    for (StartupProfileRule startupItem : startupProfileForWriting.getRules()) {
       startupItem.accept(
           startupClass ->
               collectStartupItems(startupClass, indexedItemCollection, virtualFileDefinitions),
@@ -93,7 +93,7 @@
   }
 
   private void collectStartupItems(
-      StartupClass startupClass,
+      StartupProfileClassRule startupClass,
       StartupIndexedItemCollection indexedItemCollection,
       Map<DexType, DexProgramClass> virtualFileDefinitions) {
     DexProgramClass definition = virtualFileDefinitions.get(startupClass.getReference());
@@ -116,7 +116,7 @@
   }
 
   private void collectStartupItems(
-      StartupMethod startupMethod,
+      StartupProfileMethodRule 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 aea0add..cf56867 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -1335,7 +1335,7 @@
 
       private static Predicate<DexProgramClass> getStartupClassPredicate(
           StartupProfile startupProfile) {
-        return clazz -> startupProfile.contains(clazz.getType());
+        return clazz -> startupProfile.containsClassRule(clazz.getType());
       }
 
       public List<DexProgramClass> getStartupClasses() {
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupProfile.java b/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupProfile.java
index 36e470b..a2e3fe9 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupProfile.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupProfile.java
@@ -4,13 +4,16 @@
 
 package com.android.tools.r8.experimental.startup;
 
-import com.android.tools.r8.experimental.startup.profile.StartupItem;
+import com.android.tools.r8.experimental.startup.profile.StartupProfileClassRule;
+import com.android.tools.r8.experimental.startup.profile.StartupProfileMethodRule;
+import com.android.tools.r8.experimental.startup.profile.StartupProfileRule;
 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;
 import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import java.util.Collection;
 import java.util.Collections;
 
@@ -19,17 +22,34 @@
   EmptyStartupProfile() {}
 
   @Override
-  public boolean contains(DexMethod method) {
+  public boolean containsClassRule(DexType type) {
     return false;
   }
 
   @Override
-  public boolean contains(DexType type) {
+  public boolean containsMethodRule(DexMethod method) {
     return false;
   }
 
   @Override
-  public Collection<StartupItem> getItems() {
+  public <E1 extends Exception, E2 extends Exception> void forEachRule(
+      ThrowingConsumer<StartupProfileClassRule, E1> classRuleConsumer,
+      ThrowingConsumer<StartupProfileMethodRule, E2> methodRuleConsumer) {
+    // Intentionally empty.
+  }
+
+  @Override
+  public StartupProfileClassRule getClassRule(DexType type) {
+    return null;
+  }
+
+  @Override
+  public StartupProfileMethodRule getMethodRule(DexMethod method) {
+    return null;
+  }
+
+  @Override
+  public Collection<StartupProfileRule> getRules() {
     return Collections.emptyList();
   }
 
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 68764cd..c7ae80d 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,7 +4,7 @@
 
 package com.android.tools.r8.experimental.startup;
 
-import com.android.tools.r8.experimental.startup.profile.StartupItem;
+import com.android.tools.r8.experimental.startup.profile.StartupProfileRule;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -72,7 +72,7 @@
 
   private Set<DexReference> computeStartupItems() {
     Set<DexReference> startupItems = Sets.newIdentityHashSet();
-    for (StartupItem startupItem : startupProfile.getItems()) {
+    for (StartupProfileRule startupItem : startupProfile.getRules()) {
       startupItem.accept(
           startupClass -> startupItems.add(startupClass.getReference()),
           startupMethod -> startupItems.add(startupMethod.getReference()));
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupProfile.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfile.java
index fe54f24..3e4f660 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupProfile.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfile.java
@@ -6,18 +6,18 @@
 
 import com.android.tools.r8.TextInputStream;
 import com.android.tools.r8.experimental.startup.profile.NonEmptyStartupProfile;
-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.StartupProfileClassRule;
+import com.android.tools.r8.experimental.startup.profile.StartupProfileMethodRule;
+import com.android.tools.r8.experimental.startup.profile.StartupProfileRule;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexReference;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.profile.AbstractProfile;
+import com.android.tools.r8.profile.AbstractProfileRule;
 import com.android.tools.r8.profile.art.ArtProfileBuilderUtils;
 import com.android.tools.r8.profile.art.HumanReadableArtProfileParser;
 import com.android.tools.r8.profile.art.HumanReadableArtProfileParserBuilder;
@@ -35,7 +35,8 @@
 import java.util.List;
 import java.util.function.Consumer;
 
-public abstract class StartupProfile {
+public abstract class StartupProfile
+    implements AbstractProfile<StartupProfileClassRule, StartupProfileMethodRule> {
 
   protected StartupProfile() {}
 
@@ -57,7 +58,7 @@
       return empty();
     }
     StartupProfile.Builder builder = StartupProfile.builder();
-    for (StartupItem startupItem : startupProfile.getItems()) {
+    for (StartupProfileRule startupItem : startupProfile.getRules()) {
       builder.addStartupItem(startupItem);
     }
     return builder.build();
@@ -78,7 +79,7 @@
   public static StartupProfile merge(Collection<StartupProfile> startupProfiles) {
     Builder builder = builder();
     for (StartupProfile startupProfile : startupProfiles) {
-      startupProfile.getItems().forEach(builder::addStartupItem);
+      startupProfile.getRules().forEach(builder::addStartupItem);
     }
     return builder.build();
   }
@@ -121,11 +122,7 @@
     return StartupProfile.merge(startupProfiles);
   }
 
-  public abstract boolean contains(DexMethod method);
-
-  public abstract boolean contains(DexType type);
-
-  public abstract Collection<StartupItem> getItems();
+  public abstract Collection<StartupProfileRule> getRules();
 
   public abstract boolean isEmpty();
 
@@ -136,14 +133,18 @@
   public abstract StartupProfile withoutPrunedItems(
       PrunedItems prunedItems, SyntheticItems syntheticItems);
 
-  public static class Builder implements StartupProfileBuilder {
+  public static class Builder
+      implements AbstractProfile.Builder<
+              StartupProfileClassRule, StartupProfileMethodRule, StartupProfile, Builder>,
+          StartupProfileBuilder {
 
     private final DexItemFactory dexItemFactory;
     private final MissingStartupProfileItemsDiagnostic.Builder missingItemsDiagnosticBuilder;
     private Reporter reporter;
     private final StartupProfileProvider startupProfileProvider;
 
-    private final LinkedHashMap<DexReference, StartupItem> startupItems = new LinkedHashMap<>();
+    private final LinkedHashMap<DexReference, StartupProfileRule> startupItems =
+        new LinkedHashMap<>();
 
     Builder() {
       this.dexItemFactory = null;
@@ -163,10 +164,26 @@
     }
 
     @Override
+    public Builder addRule(AbstractProfileRule rule) {
+      return addStartupItem(rule.asStartupProfileRule());
+    }
+
+    @Override
+    public Builder addClassRule(StartupProfileClassRule classRule) {
+      return addStartupItem(classRule);
+    }
+
+    @Override
+    public Builder addMethodRule(StartupProfileMethodRule methodRule) {
+      return addStartupItem(methodRule);
+    }
+
+    @Override
     public Builder addStartupClass(Consumer<StartupClassBuilder> startupClassBuilderConsumer) {
-      StartupClass.Builder startupClassBuilder = StartupClass.builder(dexItemFactory);
+      StartupProfileClassRule.Builder startupClassBuilder =
+          StartupProfileClassRule.builder(dexItemFactory);
       startupClassBuilderConsumer.accept(startupClassBuilder);
-      StartupClass startupClass = startupClassBuilder.build();
+      StartupProfileClassRule startupClass = startupClassBuilder.build();
       if (missingItemsDiagnosticBuilder.registerStartupClass(startupClass)) {
         return this;
       }
@@ -175,9 +192,10 @@
 
     @Override
     public Builder addStartupMethod(Consumer<StartupMethodBuilder> startupMethodBuilderConsumer) {
-      StartupMethod.Builder startupMethodBuilder = StartupMethod.builder(dexItemFactory);
+      StartupProfileMethodRule.Builder startupMethodBuilder =
+          StartupProfileMethodRule.builder(dexItemFactory);
       startupMethodBuilderConsumer.accept(startupMethodBuilder);
-      StartupMethod startupMethod = startupMethodBuilder.build();
+      StartupProfileMethodRule startupMethod = startupMethodBuilder.build();
       if (missingItemsDiagnosticBuilder.registerStartupMethod(startupMethod)) {
         return this;
       }
@@ -200,7 +218,7 @@
       return this;
     }
 
-    public Builder addStartupItem(StartupItem startupItem) {
+    public Builder addStartupItem(StartupProfileRule startupItem) {
       startupItems.put(startupItem.getReference(), startupItem);
       return this;
     }
@@ -219,6 +237,7 @@
       return this;
     }
 
+    @Override
     public StartupProfile build() {
       if (startupItems.isEmpty()) {
         return empty();
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
index c5ec563..e5b6b58 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java
@@ -6,7 +6,7 @@
 
 import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
 
-import com.android.tools.r8.experimental.startup.profile.StartupItem;
+import com.android.tools.r8.experimental.startup.profile.StartupProfileRule;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.profile.art.HumanReadableArtProfileParserBuilder;
@@ -61,7 +61,7 @@
     startupProfileProvider.getStartupProfile(startupProfileBuilder);
     // Serialize the startup items.
     StringBuilder resultBuilder = new StringBuilder();
-    for (StartupItem startupItem : startupProfileBuilder.build().getItems()) {
+    for (StartupProfileRule startupItem : startupProfileBuilder.build().getRules()) {
       startupItem.write(resultBuilder);
       resultBuilder.append('\n');
     }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/profile/NonEmptyStartupProfile.java b/src/main/java/com/android/tools/r8/experimental/startup/profile/NonEmptyStartupProfile.java
index 8ebd821..90a8e7d 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/profile/NonEmptyStartupProfile.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/profile/NonEmptyStartupProfile.java
@@ -13,30 +13,51 @@
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import java.util.Collection;
 import java.util.LinkedHashMap;
 
 public class NonEmptyStartupProfile extends StartupProfile {
 
-  private final LinkedHashMap<DexReference, StartupItem> startupItems;
+  private final LinkedHashMap<DexReference, StartupProfileRule> startupItems;
 
-  public NonEmptyStartupProfile(LinkedHashMap<DexReference, StartupItem> startupItems) {
+  public NonEmptyStartupProfile(LinkedHashMap<DexReference, StartupProfileRule> startupItems) {
     assert !startupItems.isEmpty();
     this.startupItems = startupItems;
   }
 
   @Override
-  public boolean contains(DexMethod method) {
+  public boolean containsMethodRule(DexMethod method) {
     return startupItems.containsKey(method);
   }
 
   @Override
-  public boolean contains(DexType type) {
+  public boolean containsClassRule(DexType type) {
     return startupItems.containsKey(type);
   }
 
   @Override
-  public Collection<StartupItem> getItems() {
+  public <E1 extends Exception, E2 extends Exception> void forEachRule(
+      ThrowingConsumer<StartupProfileClassRule, E1> classRuleConsumer,
+      ThrowingConsumer<StartupProfileMethodRule, E2> methodRuleConsumer)
+      throws E1, E2 {
+    for (StartupProfileRule rule : getRules()) {
+      rule.accept(classRuleConsumer, methodRuleConsumer);
+    }
+  }
+
+  @Override
+  public StartupProfileClassRule getClassRule(DexType type) {
+    return (StartupProfileClassRule) startupItems.get(type);
+  }
+
+  @Override
+  public StartupProfileMethodRule getMethodRule(DexMethod method) {
+    return (StartupProfileMethodRule) startupItems.get(method);
+  }
+
+  @Override
+  public Collection<StartupProfileRule> getRules() {
     return startupItems.values();
   }
 
@@ -47,24 +68,23 @@
 
   @Override
   public StartupProfile rewrittenWithLens(GraphLens graphLens) {
-    LinkedHashMap<DexReference, StartupItem> rewrittenStartupItems =
+    LinkedHashMap<DexReference, StartupProfileRule> rewrittenStartupItems =
         new LinkedHashMap<>(startupItems.size());
-    for (StartupItem startupItem : startupItems.values()) {
+    for (StartupProfileRule startupItem : startupItems.values()) {
       // TODO(b/271822426): This should account for one-to-many mappings. e.g., when a bridge is
       //  created.
       startupItem.apply(
           startupClass ->
               rewrittenStartupItems.put(
                   startupClass.getReference(),
-                  StartupClass.builder()
+                  StartupProfileClassRule.builder()
                       .setClassReference(graphLens.lookupType(startupClass.getReference()))
                       .build()),
           startupMethod ->
               rewrittenStartupItems.put(
                   startupMethod.getReference(),
-                  StartupMethod.builder()
-                      .setMethodReference(
-                          graphLens.getRenamedMethodSignature(startupMethod.getReference()))
+                  StartupProfileMethodRule.builder()
+                      .setMethod(graphLens.getRenamedMethodSignature(startupMethod.getReference()))
                       .build()));
     }
     return createNonEmpty(rewrittenStartupItems);
@@ -95,17 +115,17 @@
    */
   @Override
   public StartupProfile toStartupOrderForWriting(AppView<?> appView) {
-    LinkedHashMap<DexReference, StartupItem> rewrittenStartupItems =
+    LinkedHashMap<DexReference, StartupProfileRule> rewrittenStartupItems =
         new LinkedHashMap<>(startupItems.size());
-    for (StartupItem startupItem : startupItems.values()) {
+    for (StartupProfileRule startupItem : startupItems.values()) {
       addStartupItem(startupItem, rewrittenStartupItems, appView);
     }
     return createNonEmpty(rewrittenStartupItems);
   }
 
   private static void addStartupItem(
-      StartupItem startupItem,
-      LinkedHashMap<DexReference, StartupItem> rewrittenStartupItems,
+      StartupProfileRule startupItem,
+      LinkedHashMap<DexReference, StartupProfileRule> rewrittenStartupItems,
       AppView<?> appView) {
     startupItem.accept(
         startupClass ->
@@ -114,16 +134,18 @@
   }
 
   private static boolean addClass(
-      DexProgramClass clazz, LinkedHashMap<DexReference, StartupItem> rewrittenStartupItems) {
-    StartupItem previous =
+      DexProgramClass clazz,
+      LinkedHashMap<DexReference, StartupProfileRule> rewrittenStartupItems) {
+    StartupProfileRule previous =
         rewrittenStartupItems.put(
-            clazz.getType(), StartupClass.builder().setClassReference(clazz.getType()).build());
+            clazz.getType(),
+            StartupProfileClassRule.builder().setClassReference(clazz.getType()).build());
     return previous == null;
   }
 
   private static void addClassAndParentClasses(
       DexType type,
-      LinkedHashMap<DexReference, StartupItem> rewrittenStartupItems,
+      LinkedHashMap<DexReference, StartupProfileRule> rewrittenStartupItems,
       AppView<?> appView) {
     DexProgramClass definition = appView.app().programDefinitionFor(type);
     if (definition != null) {
@@ -133,7 +155,7 @@
 
   private static void addClassAndParentClasses(
       DexProgramClass clazz,
-      LinkedHashMap<DexReference, StartupItem> rewrittenStartupItems,
+      LinkedHashMap<DexReference, StartupProfileRule> rewrittenStartupItems,
       AppView<?> appView) {
     if (addClass(clazz, rewrittenStartupItems)) {
       addParentClasses(clazz, rewrittenStartupItems, appView);
@@ -142,7 +164,7 @@
 
   private static void addParentClasses(
       DexProgramClass clazz,
-      LinkedHashMap<DexReference, StartupItem> rewrittenStartupItems,
+      LinkedHashMap<DexReference, StartupProfileRule> rewrittenStartupItems,
       AppView<?> appView) {
     clazz.forEachImmediateSupertype(
         supertype -> addClassAndParentClasses(supertype, rewrittenStartupItems, appView));
@@ -150,9 +172,9 @@
 
   @Override
   public StartupProfile withoutPrunedItems(PrunedItems prunedItems, SyntheticItems syntheticItems) {
-    LinkedHashMap<DexReference, StartupItem> rewrittenStartupItems =
+    LinkedHashMap<DexReference, StartupProfileRule> rewrittenStartupItems =
         new LinkedHashMap<>(startupItems.size());
-    for (StartupItem startupItem : startupItems.values()) {
+    for (StartupProfileRule startupItem : startupItems.values()) {
       // Only prune non-synthetic classes, since the pruning of a class does not imply that all
       // classes synthesized from it have been pruned.
       startupItem.accept(
@@ -170,7 +192,8 @@
     return createNonEmpty(rewrittenStartupItems);
   }
 
-  private StartupProfile createNonEmpty(LinkedHashMap<DexReference, StartupItem> startupItems) {
+  private StartupProfile createNonEmpty(
+      LinkedHashMap<DexReference, StartupProfileRule> startupItems) {
     if (startupItems.isEmpty()) {
       assert false;
       return empty();
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
deleted file mode 100644
index 6ab6ec1..0000000
--- a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupItem.java
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.experimental.startup.profile;
-
-import com.android.tools.r8.graph.DexReference;
-import java.io.IOException;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-public abstract class StartupItem {
-
-  public abstract void accept(
-      Consumer<StartupClass> classConsumer, Consumer<StartupMethod> methodConsumer);
-
-  public abstract <T> T apply(
-      Function<StartupClass, T> classFunction, Function<StartupMethod, T> methodFunction);
-
-  public abstract DexReference getReference();
-
-  public abstract void write(Appendable appendable) throws IOException;
-}
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
deleted file mode 100644
index 5d7448b..0000000
--- a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupMethod.java
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.experimental.startup.profile;
-
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.references.MethodReference;
-import com.android.tools.r8.startup.StartupMethodBuilder;
-import com.android.tools.r8.utils.MethodReferenceUtils;
-import java.io.IOException;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-public class StartupMethod extends StartupItem {
-
-  private final DexMethod method;
-
-  StartupMethod(DexMethod method) {
-    this.method = method;
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static Builder builder(DexItemFactory dexItemFactory) {
-    return new Builder(dexItemFactory);
-  }
-
-  @Override
-  public void accept(Consumer<StartupClass> classConsumer, Consumer<StartupMethod> methodConsumer) {
-    methodConsumer.accept(this);
-  }
-
-  @Override
-  public <T> T apply(
-      Function<StartupClass, T> classFunction, Function<StartupMethod, T> methodFunction) {
-    return methodFunction.apply(this);
-  }
-
-  @Override
-  public DexMethod getReference() {
-    return method;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-    StartupMethod that = (StartupMethod) o;
-    return method == that.method;
-  }
-
-  @Override
-  public int hashCode() {
-    return method.hashCode();
-  }
-
-  @Override
-  public void write(Appendable appendable) throws IOException {
-    appendable.append(method.toSmaliString());
-  }
-
-  public static class Builder implements StartupMethodBuilder {
-
-    private final DexItemFactory dexItemFactory;
-
-    private DexMethod method;
-
-    Builder() {
-      this(null);
-    }
-
-    Builder(DexItemFactory dexItemFactory) {
-      this.dexItemFactory = dexItemFactory;
-    }
-
-    @Override
-    public Builder setMethodReference(MethodReference classReference) {
-      assert dexItemFactory != null;
-      return setMethodReference(MethodReferenceUtils.toDexMethod(classReference, dexItemFactory));
-    }
-
-    public Builder setMethodReference(DexMethod method) {
-      this.method = method;
-      return this;
-    }
-
-    public StartupMethod build() {
-      return new StartupMethod(method);
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupClass.java b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileClassRule.java
similarity index 68%
rename from src/main/java/com/android/tools/r8/experimental/startup/profile/StartupClass.java
rename to src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileClassRule.java
index 8e4efdb..c474ff2 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/StartupProfileClassRule.java
@@ -6,18 +6,20 @@
 
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.profile.AbstractProfileClassRule;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.startup.StartupClassBuilder;
 import com.android.tools.r8.utils.ClassReferenceUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import java.io.IOException;
-import java.util.function.Consumer;
 import java.util.function.Function;
 
-public class StartupClass extends StartupItem {
+public class StartupProfileClassRule extends StartupProfileRule
+    implements AbstractProfileClassRule {
 
   private final DexType type;
 
-  StartupClass(DexType type) {
+  StartupProfileClassRule(DexType type) {
     this.type = type;
   }
 
@@ -30,13 +32,17 @@
   }
 
   @Override
-  public void accept(Consumer<StartupClass> classConsumer, Consumer<StartupMethod> methodConsumer) {
+  public <E1 extends Exception, E2 extends Exception> void accept(
+      ThrowingConsumer<StartupProfileClassRule, E1> classConsumer,
+      ThrowingConsumer<StartupProfileMethodRule, E2> methodConsumer)
+      throws E1 {
     classConsumer.accept(this);
   }
 
   @Override
   public <T> T apply(
-      Function<StartupClass, T> classFunction, Function<StartupMethod, T> methodFunction) {
+      Function<StartupProfileClassRule, T> classFunction,
+      Function<StartupProfileMethodRule, T> methodFunction) {
     return classFunction.apply(this);
   }
 
@@ -53,7 +59,7 @@
     if (o == null || getClass() != o.getClass()) {
       return false;
     }
-    StartupClass that = (StartupClass) o;
+    StartupProfileClassRule that = (StartupProfileClassRule) o;
     return type == that.type;
   }
 
@@ -67,7 +73,8 @@
     appendable.append(getReference().toDescriptorString());
   }
 
-  public static class Builder implements StartupClassBuilder {
+  public static class Builder
+      implements AbstractProfileClassRule.Builder<StartupProfileClassRule>, StartupClassBuilder {
 
     private final DexItemFactory dexItemFactory;
 
@@ -92,8 +99,9 @@
       return this;
     }
 
-    public StartupClass build() {
-      return new StartupClass(type);
+    @Override
+    public StartupProfileClassRule build() {
+      return new StartupProfileClassRule(type);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileMethodRule.java b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileMethodRule.java
new file mode 100644
index 0000000..0a3a36a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileMethodRule.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.experimental.startup.profile;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.profile.AbstractProfileMethodRule;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.startup.StartupMethodBuilder;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import java.io.IOException;
+import java.util.function.Function;
+
+public class StartupProfileMethodRule extends StartupProfileRule
+    implements AbstractProfileMethodRule {
+
+  private final DexMethod method;
+
+  StartupProfileMethodRule(DexMethod method) {
+    this.method = method;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static Builder builder(DexItemFactory dexItemFactory) {
+    return new Builder(dexItemFactory);
+  }
+
+  @Override
+  public <E1 extends Exception, E2 extends Exception> void accept(
+      ThrowingConsumer<StartupProfileClassRule, E1> classConsumer,
+      ThrowingConsumer<StartupProfileMethodRule, E2> methodConsumer)
+      throws E2 {
+    methodConsumer.accept(this);
+  }
+
+  @Override
+  public <T> T apply(
+      Function<StartupProfileClassRule, T> classFunction,
+      Function<StartupProfileMethodRule, T> methodFunction) {
+    return methodFunction.apply(this);
+  }
+
+  @Override
+  public DexMethod getReference() {
+    return method;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    StartupProfileMethodRule that = (StartupProfileMethodRule) o;
+    return method == that.method;
+  }
+
+  @Override
+  public int hashCode() {
+    return method.hashCode();
+  }
+
+  @Override
+  public void write(Appendable appendable) throws IOException {
+    appendable.append(method.toSmaliString());
+  }
+
+  public static class Builder
+      implements AbstractProfileMethodRule.Builder<StartupProfileMethodRule, Builder>,
+          StartupMethodBuilder {
+
+    private final DexItemFactory dexItemFactory;
+
+    private DexMethod method;
+
+    Builder() {
+      this(null);
+    }
+
+    Builder(DexItemFactory dexItemFactory) {
+      this.dexItemFactory = dexItemFactory;
+    }
+
+    @Override
+    public boolean isGreaterThanOrEqualTo(Builder builder) {
+      return true;
+    }
+
+    @Override
+    public Builder join(Builder builder) {
+      return this;
+    }
+
+    @Override
+    public Builder join(StartupProfileMethodRule methodRule) {
+      return this;
+    }
+
+    @Override
+    public Builder setMethod(DexMethod method) {
+      this.method = method;
+      return this;
+    }
+
+    @Override
+    public Builder setMethodReference(MethodReference classReference) {
+      assert dexItemFactory != null;
+      return setMethod(MethodReferenceUtils.toDexMethod(classReference, dexItemFactory));
+    }
+
+    @Override
+    public StartupProfileMethodRule build() {
+      return new StartupProfileMethodRule(method);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileRule.java b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileRule.java
new file mode 100644
index 0000000..200d364
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfileRule.java
@@ -0,0 +1,33 @@
+// 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.DexReference;
+import com.android.tools.r8.profile.AbstractProfileRule;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import java.io.IOException;
+import java.util.function.Function;
+
+public abstract class StartupProfileRule
+    implements AbstractProfileRule, Comparable<StartupProfileRule> {
+
+  public abstract <E1 extends Exception, E2 extends Exception> void accept(
+      ThrowingConsumer<StartupProfileClassRule, E1> classConsumer,
+      ThrowingConsumer<StartupProfileMethodRule, E2> methodConsumer)
+      throws E1, E2;
+
+  public abstract <T> T apply(
+      Function<StartupProfileClassRule, T> classFunction,
+      Function<StartupProfileMethodRule, T> methodFunction);
+
+  @Override
+  public final int compareTo(StartupProfileRule rule) {
+    return getReference().compareTo(rule.getReference());
+  }
+
+  public abstract DexReference getReference();
+
+  public abstract void write(Appendable appendable) throws IOException;
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/rewriting/StartupProfileAdditions.java b/src/main/java/com/android/tools/r8/experimental/startup/rewriting/StartupProfileAdditions.java
new file mode 100644
index 0000000..1f12cb3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/startup/rewriting/StartupProfileAdditions.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2023, 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.rewriting;
+
+import com.android.tools.r8.experimental.startup.StartupProfile;
+import com.android.tools.r8.experimental.startup.profile.StartupProfileClassRule;
+import com.android.tools.r8.experimental.startup.profile.StartupProfileMethodRule;
+import com.android.tools.r8.experimental.startup.profile.StartupProfileRule;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.profile.AbstractProfileRule;
+import com.android.tools.r8.profile.art.rewriting.ProfileAdditions;
+import java.util.Comparator;
+
+// TODO(b/271822426): Use to include synthetics in startup profile.
+public class StartupProfileAdditions
+    extends ProfileAdditions<
+        StartupProfileAdditions,
+        StartupProfileClassRule,
+        StartupProfileClassRule.Builder,
+        StartupProfileMethodRule,
+        StartupProfileMethodRule.Builder,
+        StartupProfileRule,
+        StartupProfile,
+        StartupProfile.Builder> {
+
+  StartupProfileAdditions(StartupProfile profile) {
+    super(profile);
+  }
+
+  @Override
+  public StartupProfileAdditions create() {
+    return new StartupProfileAdditions(profile);
+  }
+
+  @Override
+  public StartupProfileClassRule.Builder createClassRuleBuilder(DexType type) {
+    return StartupProfileClassRule.builder().setClassReference(type);
+  }
+
+  @Override
+  public StartupProfileMethodRule.Builder createMethodRuleBuilder(DexMethod method) {
+    return StartupProfileMethodRule.builder().setMethod(method);
+  }
+
+  @Override
+  public StartupProfile.Builder createProfileBuilder() {
+    return StartupProfile.builder();
+  }
+
+  @Override
+  public Comparator<AbstractProfileRule> getRuleComparator() {
+    return Comparator.comparing(AbstractProfileRule::asStartupProfileRule);
+  }
+
+  @Override
+  public StartupProfileAdditions self() {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
index 88d2a35..db1c46f 100644
--- a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
+++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
@@ -175,7 +175,7 @@
       feature = classToFeatureSplitMap.getOrDefault(type, FeatureSplit.BASE);
     }
     if (feature.isBase()) {
-      return !startupProfile.contains(type)
+      return !startupProfile.containsClassRule(type)
               || options.getStartupOptions().isStartupBoundaryOptimizationsEnabled()
           ? FeatureSplit.BASE
           : FeatureSplit.BASE_STARTUP;
diff --git a/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java b/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
index bf3fc09..8badf5b 100644
--- a/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
+++ b/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
@@ -77,8 +77,8 @@
     } else if (callerIsStartupMethod.isFalse()) {
       // If the caller is not a startup method, then only allow inlining if the caller is not a
       // startup class or the callee is a startup class.
-      if (startupProfile.contains(caller.getHolderType())
-          && !startupProfile.contains(callee.getHolderType())) {
+      if (startupProfile.containsClassRule(caller.getHolderType())
+          && !startupProfile.containsClassRule(callee.getHolderType())) {
         return false;
       }
     }
@@ -91,7 +91,7 @@
       // accurate result in this case.
       return OptionalBool.unknown();
     }
-    return OptionalBool.of(startupProfile.contains(method.getReference()));
+    return OptionalBool.of(startupProfile.containsMethodRule(method.getReference()));
   }
 
   public static boolean isSafeForVerticalClassMerging(
@@ -114,8 +114,8 @@
     // If the source class is a startup class then require that the target class is also a startup
     // class.
     StartupProfile startupProfile = appView.getStartupOrder();
-    if (startupProfile.contains(sourceClass.getType())
-        && !startupProfile.contains(targetClass.getType())) {
+    if (startupProfile.containsClassRule(sourceClass.getType())
+        && !startupProfile.containsClassRule(targetClass.getType())) {
       return false;
     }
     return true;
diff --git a/src/main/java/com/android/tools/r8/profile/AbstractProfile.java b/src/main/java/com/android/tools/r8/profile/AbstractProfile.java
index 8265aa2..87b5656 100644
--- a/src/main/java/com/android/tools/r8/profile/AbstractProfile.java
+++ b/src/main/java/com/android/tools/r8/profile/AbstractProfile.java
@@ -30,7 +30,7 @@
       Profile extends AbstractProfile<ClassRule, MethodRule>,
       ProfileBuilder extends Builder<ClassRule, MethodRule, Profile, ProfileBuilder>> {
 
-    ProfileBuilder addRule(AbstractProfileRule profileRule);
+    ProfileBuilder addRule(AbstractProfileRule rule);
 
     ProfileBuilder addClassRule(ClassRule classRule);
 
diff --git a/src/main/java/com/android/tools/r8/profile/AbstractProfileRule.java b/src/main/java/com/android/tools/r8/profile/AbstractProfileRule.java
index c9f0d4c..ad28bfd 100644
--- a/src/main/java/com/android/tools/r8/profile/AbstractProfileRule.java
+++ b/src/main/java/com/android/tools/r8/profile/AbstractProfileRule.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.profile;
 
+import com.android.tools.r8.experimental.startup.profile.StartupProfileRule;
 import com.android.tools.r8.profile.art.ArtProfileRule;
 
 public interface AbstractProfileRule {
@@ -12,4 +13,9 @@
   default ArtProfileRule asArtProfileRule() {
     return (ArtProfileRule) this;
   }
+
+  @SuppressWarnings("unchecked")
+  default StartupProfileRule asStartupProfileRule() {
+    return (StartupProfileRule) this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
index a1761ec..7ab727d 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
@@ -292,12 +292,7 @@
 
     @Override
     public Builder addRule(AbstractProfileRule rule) {
-      if (rule instanceof ArtProfileClassRule) {
-        return addClassRule((ArtProfileClassRule) rule);
-      } else {
-        assert rule instanceof ArtProfileMethodRule;
-        return addMethodRule((ArtProfileMethodRule) rule);
-      }
+      return addRule(rule.asArtProfileRule());
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ProfileAdditions.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ProfileAdditions.java
index 195963e..d4bd1bc 100644
--- a/src/main/java/com/android/tools/r8/profile/art/rewriting/ProfileAdditions.java
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ProfileAdditions.java
@@ -77,7 +77,7 @@
     void removeMovedMethodRule(DexMethod oldMethod, ProgramMethod newMethod);
   }
 
-  Profile profile;
+  protected Profile profile;
 
   final Map<DexType, ClassRuleBuilder> classRuleAdditions = new ConcurrentHashMap<>();
   final Map<DexMethod, MethodRuleBuilder> methodRuleAdditions = new ConcurrentHashMap<>();
@@ -86,7 +86,7 @@
   private final NestedMethodRuleAdditionsGraph nestedMethodRuleAdditionsGraph =
       new NestedMethodRuleAdditionsGraph();
 
-  ProfileAdditions(Profile profile) {
+  protected ProfileAdditions(Profile profile) {
     this.profile = profile;
   }
 
diff --git a/src/main/java/com/android/tools/r8/startup/diagnostic/MissingStartupProfileItemsDiagnostic.java b/src/main/java/com/android/tools/r8/startup/diagnostic/MissingStartupProfileItemsDiagnostic.java
index 272684d..0c33a74 100644
--- a/src/main/java/com/android/tools/r8/startup/diagnostic/MissingStartupProfileItemsDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/startup/diagnostic/MissingStartupProfileItemsDiagnostic.java
@@ -6,8 +6,8 @@
 
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.Keep;
-import com.android.tools.r8.experimental.startup.profile.StartupClass;
-import com.android.tools.r8.experimental.startup.profile.StartupMethod;
+import com.android.tools.r8.experimental.startup.profile.StartupProfileClassRule;
+import com.android.tools.r8.experimental.startup.profile.StartupProfileMethodRule;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
@@ -85,7 +85,7 @@
       return !missingStartupItems.isEmpty();
     }
 
-    public boolean registerStartupClass(StartupClass startupClass) {
+    public boolean registerStartupClass(StartupProfileClassRule startupClass) {
       if (definitions != null && !definitions.hasDefinitionFor(startupClass.getReference())) {
         addMissingStartupItem(startupClass.getReference());
         return true;
@@ -93,7 +93,7 @@
       return false;
     }
 
-    public boolean registerStartupMethod(StartupMethod startupMethod) {
+    public boolean registerStartupMethod(StartupProfileMethodRule startupMethod) {
       if (definitions != null && !definitions.hasDefinitionFor(startupMethod.getReference())) {
         addMissingStartupItem(startupMethod.getReference());
         return true;
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 901629a..f22ce21 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -22,7 +22,7 @@
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.experimental.startup.StartupProfile;
-import com.android.tools.r8.experimental.startup.profile.StartupItem;
+import com.android.tools.r8.experimental.startup.profile.StartupProfileRule;
 import com.android.tools.r8.origin.EmbeddedOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.references.Reference;
@@ -812,9 +812,9 @@
 
     // Verify we found the same rule.
     StartupProfile startupProfile = startupProfileBuilder.build();
-    Collection<StartupItem> startupItems = startupProfile.getItems();
+    Collection<StartupProfileRule> startupItems = startupProfile.getRules();
     assertEquals(1, startupItems.size());
-    StartupItem startupItem = startupItems.iterator().next();
+    StartupProfileRule startupItem = startupItems.iterator().next();
     startupItem.accept(
         startupClass -> fail(),
         startupMethod -> assertEquals(profileRule, startupMethod.getReference().toSmaliString()));